Preservation of Local State in C#





5.00/5 (2 votes)
How to preserve state of local variables in 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.
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.
/// <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.
/// <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:
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.
/// <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:
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.)
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