Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

Preservation of Local State in C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
14 Mar 2023MIT4 min read 4.7K   71   7   3
How to preserve state of local variables in C#
This article deals with the preservation of the state of local variables in C# as compared to C/C++.

Introduction and Motivation

The C and C++ programming languages allow declaring local variables as static. The property of such variables is that they preserve their state between calls. To illustrate, consider the following function and test main program, created with the Visual Studio IDE as unmanaged C++ (Win32) code.

C++
int NextInt()
{
   static int n = -1;
 
   ++n; // Compute the next {int} in a sequence.
 
   return n;
} // NextInt
 
int _tmain(int argc, _TCHAR* argv[])
{
   printf( "\n" );
 
   for ( int i = 0; i < 10; ++i )
   {
      int n = NextInt();
 
      printf( "%2d ", n );
   }
   printf( "\n\n" );
 
   return 0;
}// _tmain

Upon execution, the following output is obtained in the command-prompt window, thus demonstrating that the local variable of function NextInt preserves its value between calls.

0  1  2  3  4  5  6  7  8  9

Press any key to continue . . .

The C++ compiler implements local static variables by storing them as qualified-name global variables. If, as explained in the complete source code (file StaticVariables.cpp in the attached ZIP file), the compiler is instructed to generate assembly-language code, one can verify that such is the case (code highlighted in red):

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.40219.01

    TITLE    C:\Users\Jorge\Documents\Visual Studio 2010\Projects\C++ unmanaged\StaticVariables\StaticVariables.cpp
    .686P
    .XMM
    include listing.inc
    .model    flat
 
INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES
 
_DATA    SEGMENT
?n@?1??NextInt@@YAHXZ@4HA DD 0ffffffffH            ; `NextInt'::`2'::n
_DATA    ENDS
PUBLIC    ?NextInt@@YAHXZ                    ; NextInt
EXTRN    __RTC_Shutdown:PROC
EXTRN    __RTC_InitBase:PROC
;    COMDAT rtc$TMZ
; File c:\users\jorge\documents\visual studio 2010\projects\c++ unmanaged\staticvariables\staticvariables.cpp
rtc$TMZ    SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ    ENDS
;    COMDAT rtc$IMZ
rtc$IMZ    SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
; Function compile flags: /Odtp /RTCsu /ZI
rtc$IMZ    ENDS
;    COMDAT ?NextInt@@YAHXZ
_TEXT    SEGMENT
?NextInt@@YAHXZ PROC                    ; NextInt, COMDAT
 
; 22   : {
 
    push    ebp
    mov    ebp, esp
    sub    esp, 192                ; 000000c0H
    push    ebx
    push    esi
    push    edi
    lea    edi, DWORD PTR [ebp-192]
    mov    ecx, 48                    ; 00000030H
    mov    eax, -858993460                ; ccccccccH
    rep stosd
 
; 23   :    static int n = -1;
; 24   :
; 25   :    ++n; // Compute the next {int} in a sequence.
 
    mov    eax, DWORD PTR ?n@?1??NextInt@@YAHXZ@4HA
    add    eax, 1
    mov    DWORD PTR ?n@?1??NextInt@@YAHXZ@4HA, eax
 
; 26   :
; 27   :    return n;
 
    mov    eax, DWORD PTR ?n@?1??NextInt@@YAHXZ@4HA
 
; 28   : } // NextInt
 
    pop    edi
    pop    esi
    pop    ebx
    mov    esp, ebp
    pop    ebp
    ret    0
?NextInt@@YAHXZ ENDP                    ; NextInt
_TEXT    ENDS

The local static variable of function NextInt is stored in the _DATA SEGMENT, which is used to store global variables. To avoid conflicts with static variables of the same name in other functions, the qualified name of the local variable is ?n@?1??NextInt@@YAHXZ@4HA.

“Static” Local Variables in C#

The C# programming language does not allow declaring local variables as static. However, it is possible to preserve the state of local variables between calls to a function by means of enumerators. The following C# code produces the same result obtained from the C++ code.

C#
/// <summary>Generate the next integer in a sequence.
/// </summary>
/// <returns>The generated value.
/// </returns>
static IEnumerator<int> NextInt()
{
   int n = -1;

   while ( true )
   {
      ++n;

      yield return n;
   }
}// NextInt

The C# code preserves the state of the local variable between calls, thanks to the declaration of NextInt as an IEnumerator, the enclosing of the enumeration code within an infinite loop, and the use of the yield return keywords.

On the first call to NextInt, the local variable is initialized and the infinite loop is started. Within the loop, the local variable is updated and its value (0) is returned to the caller. On the second call, execution does not re-start NextInt but continues after the yield return statement, which goes back to the beginning of the loop. The local variable is updated and its value (1) is returned to the caller. This is repeated as many times as there are calls. The preservation of the state of the local variable makes it behave as if it had been declared as static.

Additional Examples of “Static” Local Variables in C#

In addition to NextInt, two further examples illustrate how to generate the next value in a sequence of powers of 2, and the next value in a sequence of Fibonacci numbers.

C#
/// <summary>Generate the next power of 2 in a sequence.
/// </summary>
/// <returns>The generated value.
/// </returns>
static IEnumerator<int> NextPowOf2()
{
   int pow = 1;

   while ( true )
   {
      yield return pow;

      pow <<= 1;
   }
}// NextPowOf2

/// <summary>Generate the next Fibonacci number in a sequence.
/// </summary>
/// <returns>The generated value.
/// </returns>
static IEnumerator<int> NextFib()
{
   int a = 0, b = 1, t;

   while ( true )
   {
      yield return a;

      t = a + b;
      a = b;
      b = t;
   }
}// NextFib

Testing the Code

For completeness, this section describes the implementation of the main program and its auxiliary functions to test the use of “static” local variables in the preceding examples. It is clear that the enumerators must be tested the same way, namely by generating a sequence of values with each enumerator. So, an example main program might be as follows:

C#
static void Main( string[] args )
{
   TestOf( "NextInt", NextInt(), 10 );
   TestOf( "NextPowOf2", NextPowOf2(), 10 );
   TestOf( "NextFib", NextFib(), 10 );
}// Main

The definitions of the auxiliary testing function and of a function to display values are rather simple.

C#
/// <summary>Driver function to test an {IEnumerator}.
/// </summary>
/// <param name="name">Enumerator's name.</param>
/// <param name="srcEnum">{IEnumerator} to be tested.</param>
/// <param name="n">Number of values to generate.
/// </param>
static void TestOf( string name, IEnumerator<int> srcEnum, int n )
{
   Console.WriteLine( "\nTest of {0}:\n", name );

   for ( int i = 0; i < n; ++i )
   {
      if ( srcEnum.MoveNext() )
      {
         Display( srcEnum.Current );
      }
      else break;
   }
   Console.WriteLine( "\n" );
}// TestOf

/// <summary>Display a result from an {IEnumerator}.
/// </summary>
/// <param name="i">Value to display.
/// </param>
static void Display( int i )
{
   Console.Write( "{0,3} ", i );
}// Display

Upon execution of function Main, the output is as follows. As can be seen, the results are as expected.

Test of NextInt:

  0   1   2   3   4   5   6   7   8   9

Test of NextPowOf2:

  1   2   4   8  16  32  64 128 256 512

Test of NextFib:

  0   1   1   2   3   5   8  13  21  34

Press any key to continue . . .

As shown by the examples, the technique should be applied to one-argument functions. At present, it is not immediately apparent how to extend the implementation to functions of more than one argument. For example, consider the two-argument Ackermann function, defined as:

Image 1

The following partial table of the values of A(m, n) shows that there is no unique way to define an enumerator NextAck (next Ackermann number) for arbitrary values of the arguments m and n. (The up arrows in the table are Knuth’s exponential notation.)

Image 2

Conclusion

This article has illustrated that, even though C# does not allow the declaration of static local variables, as can be done in C and C++, it is possible to implement the same behavior by means of enumerators.

Using the Code

The complete source code is in the attached ZIP file. It contains two files: StaticVariables.cpp and Program.cs. In the Visual Studio IDE, create a Win32 console application with the name StaticVariables, and replace the generated code with the one from the ZIP file. Follow the instructions in the comment to instruct the compiler to generate the assembly-language code. For the C# case, create a Windows console application and replace the generated code with the one in Program.cs from the ZIP file. In both cases, build the solution and start it without debugging. The output shown in the command-prompt window should be as shown in this article.

History

  • 13th March, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugNot truly static Pin
Daniele Rota Nodari15-Mar-23 6:41
Daniele Rota Nodari15-Mar-23 6:41 
GeneralNot quite equivalent Pin
John Brett 202114-Mar-23 6:44
John Brett 202114-Mar-23 6:44 
GeneralRe: Not quite equivalent Pin
Kenneth Haugland14-Mar-23 19:06
mvaKenneth Haugland14-Mar-23 19:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.