All About Statics

Get a Charge From Statics with Seven Essential Programming Tips

K. Scott Allen

This article discusses:

  • The performance and behavior of type constructors
  • Static members and thread safety
  • Static classes in the .NET Framework 2.0
  • Best practices for shared members of a type
This article uses the following technologies:
.NET Framework, C#, Visual Basic

Code download available at:StaticsinNET.exe(150 KB)

Contents

The Price of Being Precise
The Exception to the Rule
Constructor Locks
Static Reflection
Static Classes in the .NET Framework 2.0
Static Locals
Static Illusions
Static Conclusions

It is rare that you would work on a .NET-based application and not encounter a type with a Shared method or static field. Since these abstractions have special behaviors, there are important questions to ask about the implementation. When will the runtime initialize the static fields? Is this method thread safe? Could this class cause a bottleneck?

In this article, I will examine seven characteristics of statics that you should know. I will touch on the finer points of static constructors and how the C# and Visual Basic® compilers work together with the runtime to implement additional safety behind the scenes. By the end of the article, you will come away with best prac-tices for the use of static members and static classes in your apps.

The Price of Being Precise

Type constructors are also known as type initializers. They can initialize the state of a type by initializing static or shared fields to a predefined or computed value. A type will gain a type initializer in two scenarios. The first scenario is when a developer explicitly adds a type constructor using the Shared keyword on a New subroutine in Visual Basic, or by adding a static, parameterless method with the same name as the type in C#. The second scenario is when a type has a initializer for a static field, in which case the compiler adds a type constructor behind the scenes. Figure 1 shows these two scenarios with Visual Basic .NET.

Figure 1 Implicit and Explicit Static Constructors

' This type has an explicit type initializer Class ExplicitConstructor Shared Sub New() _message = "Hello World" End Sub Public Shared ReadOnly Property Message() As String Get Return _message End Get End Property Private Shared _message As String End Class ' This type has an implicit type initializer Class ImplicitConstructor Private Shared _message As String = "Hello World" Public Shared ReadOnly Property Message() As String Get Return _message End Get End Property End Class

If you compile the classes in Figure 1 and run the resulting assembly through the FxCop tool, you will find that it produces a critical warning about the ExplicitConstructor class. The warning is "Do not declare explicit static constructors." The rule description tells you that an explicit static constructor results in code that performs worse. The recommendation from FxCop is to initialize static fields where they are declared. But instead of taking the recommendation at face value, let's dig into the assembly using a disassembler or decompiler, like ILDASM, to really understand why there is a performance difference.

In Microsoft® intermediate language (MSIL), type constructors will have the name of .cctor (short for class constructor). The .cctor methods for ExplicitConstructor and ImplicitConstructor are shown in Figure 2.

Figure 2 Disassembled Implicit and Explicit Static

Static1.cctor

.method private specialname rtspecialname static void .cctor() cil managed { // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "Hello World" IL_0006: stsfld string BeforeFieldInitVB.ImplicitConstructor::_message IL_000b: nop IL_000c: ret } // end of method ExplicitConstructor::.cctor

Static2.cctor

.method private specialname rtspecialname static void .cctor() cil managed { // Code size 12 (0xc) .maxstack 8 IL_0000: ldstr "Hello World" IL_0005: stsfld string BeforeFieldInitVB.ExplicitConstructor::_message IL_000a: nop IL_000b: ret } // end of method ImplicitConstructor::.cctor

The MSIL in Figure 2 does not offer any hints about the performance difference. The compiler has given you almost identical code for the implicit type constructor in ImplicitConstructor and the explicit type constructor in ExplicitConstructor. If you dig a bit further with ILDASM, you will see a difference in the metadata of the two classes:

.class private auto ansi ExplicitConstructor extends [mscorlib]System.Object { } // end of class ExplicitConstructor .class private auto ansi beforefieldinit ImplicitConstructor extends [mscorlib]System.Object { } // end of class ImplicitConstructor

Notice that ImplicitConstructor has an additional metadata flag named beforefieldinit. This flag allows the runtime to execute the type constructor method at any time it chooses, as long as the method executes before the first access to a static field for the type. In other words, beforefieldinit gives the runtime a license to perform aggressive optimizations. Without beforefieldinit, the runtime must run the type constructor at a precise time—just before the first access to static or instance fields and methods of the type. When an explicit type constructor is present, the compilers will not mark the type with beforefieldinit, and the precise timing restrictions lead to the performance drop hinted at by FxCop. This rule holds in both C# and Visual Basic. You can exaggerate the performance variations with the code in Figure 3.

Figure 3 Performance Testing Static Constructors

Module Module1 Sub Main() TestSharedPropertyAccess() Console.ReadLine() End Sub Sub TestSharedPropertyAccess() Dim begin As DateTime = DateTime.Now For i as Integer = 0 To iterations Dim s As String = ExplicitConstructor.Message Next WriteResult(DateTime.Now.Subtract(begin), _ "TestStaticPropertyAccess : ExplicitConstructor") begin = DateTime.Now For i As Integer = 0 To iterations Dim s As String = ImplicitConstructor.Message Next WriteResult(DateTime.Now.Subtract(begin), _ "TestStaticPropertyAccess : ImplicitConstructor") End Sub Sub WriteResult(ByVal span As TimeSpan, ByVal message As String) Console.WriteLine("{0} took {1} ms", _ message, span.TotalMilliseconds) End Sub Dim iterations As Integer = Int32.MaxValue - 1 End Module

You don't need a high-resolution timer to see the difference in speed. On my 2.8GHz Pentium 4, the first loop (with ExplicitConstructor) executes approximately eight times slower than the second loop (with ImplicitConstructor). The checks that the runtime performs in order to run the type initializer at a precise time adds overhead inside of the loop, while beforefieldinit relaxes the rules and allows the runtime to hoist these checks outside of the loop.

The code in Figure 3 is a worst-case scenario. You'll need to evaluate the tradeoffs in using an explicit type constructor. Few classes will actually need the type constructor to execute at a precise time, so in most cases it makes sense to allow the compiler to add the beforefieldinit flag and avoid explicit type constructors.

The Exception to the Rule

Another behavior unique to type constructors is how the runtime manages exceptions in a type constructor. When an exception is thrown, the runtime will begin to look for the nearest catch clause whose filters specify that it can handle the exception. Now, consider the C# console mode program in Figure 4.

Figure 4 Static Constructor Exceptions

using System; namespace Exceptions { class Class1 { [STAThread] static void Main(string[] args) { string message = String.Empty; for(int i = 0; i < 2; i++) { try { message = ExceptionFromStaticCctor.StaticMessage; } catch(ApplicationException e) { Console.WriteLine("Caught ApplicationException: "); Console.WriteLine(e.Message); } catch(Exception e) { Console.WriteLine("Caught Exception: "); Console.WriteLine(" " + e.Message); Console.WriteLine(" " + e.InnerException.Message); } finally { Console.WriteLine("Message = {0}", message); } } } } class ExceptionFromStaticCctor { static ExceptionFromStaticCctor() { Console.WriteLine("In ExceptionFromStaticCctor cctor"); throw new ApplicationException("ExceptionFromStaticCctor always throws!"); } public static string StaticMessage { get { return "Hello World"; } } public string InstanceMessage { get { return "Hello World"; } } } }

The program will produce the results in Figure 5. There are two behaviors to note. First, the program did not hit the catch clause for an exception of type ApplicationException, even though that's the type of exception thrown from the static constructor. The runtime will stop any exceptions trying to leave a type constructor and wrap the exception inside of a new TypeInitializationException object. The original exception thrown inside the type constructor is then available from the InnerException property of the TypeInitializationException object.

Figure 5 TypeInitializationException

One reason this special behavior might occur is that the second time you try to access a static property of the type, the runtime does not try to invoke the type constructor again, but throws the same exception observed in the first iteration. The runtime does not give a type constructor a second chance. The same rules hold true if the exception is thrown from a static field initializer. As you saw earlier, the code for a static field initializer executes inside an implicit type constructor.

A TypeInitializationException can be a fatal error in an application since it renders a type useless. You should plan to catch any exceptions inside of a type constructor if there is any possibility of recovering from the error, and you should allow an application to terminate if the error cannot be reconciled.

Constructor Locks

Type constructors have an additional quirk. This comes from the Common Language Infrastructure (CLI) spec that guarantees a type constructor will execute only once, unless explicitly invoked by user code. To enforce this guarantee in a multithreaded environment requires a lock to synchronize threads. A runtime thread must acquire this lock before invoking the type constructor.

A program taking a lock must take great care not to create a deadlock situation. To see how it can happen, try to put the runtime into a deadlocked state using the sinister code in Figure 6. This sends thread A into the type constructor for Static1, and thread B into the type constructor for Static2, then puts both the threads to sleep. Thread A will wake up and need access to Static2, which B has locked. Thread B will wake up and need access to Static1, which A has locked. Thread A needs the lock held by Thread B, and Thread B needs the lock held by Thread A. This is a classic deadlock scenario.

Figure 6 Attempting to Deadlock with Static Constructors

using System; using System.Threading; namespace TypeConstructorLock { class Class1 { [STAThread] static void Main(string[] args) { Thread threadA = new Thread(new ThreadStart(TouchStatic1)); threadA.Name = "Thread A"; Thread threadB = new Thread(new ThreadStart(TouchStatic2)); threadB.Name = "Thread B"; threadA.Start(); threadB.Start(); threadA.Join(); threadB.Join(); } static void TouchStatic1() { string s = Static1.Message; } static void TouchStatic2() { string s = Static2.Message; } } class Static1 { static Static1() { Console.WriteLine("Begin Static1 .cctor on thread {0}", Thread.CurrentThread.Name); Thread.Sleep(5000); Console.WriteLine("Static1 has a message from Static2: {0}", Static2.Message); message = "Hello From Static1"; Console.WriteLine("Exit Static1 .cctor on thread {0}", Thread.CurrentThread.Name); } static public string Message { get { return message; } } static string message = "blank"; } class Static2 { static Static2() { Console.WriteLine("Begin Static2 .cctor on thread {0}", Thread.CurrentThread.Name); Thread.Sleep(5000); Console.WriteLine("Static2 has a message from Static1: {0}", Static1.Message); message = "Hello From Static2"; Console.WriteLine("Exit Static2 .cctor on thread {0}", Thread.CurrentThread.Name); } static public string Message { get { return message; } } static string message = "blank"; } }

The application in Figure 6 does not deadlock, but instead produces the results that are shown in Figure 7. It turns out the CLI specification also guarantees that the runtime will not allow type constructors to create a deadlock situation, unless additional locks are explicitly taken by user code.

Figure 7 CLI and Deadlock Avoidance

Figure 7** CLI and Deadlock Avoidance **

In the program run shown in Figure 7, the runtime avoided deadlock by allowing Static2 to access the static Message property of Static1 before the type constructor for Static1 finished execution. The message from Static1 should have been "Hello From Static1," but instead you see that the message is "blank". This program also demonstrates how static field initializers execute before the code inside an explicit type constructor.

You might recognize that this problem is similar to the C++ static initialization fiasco. The rule of thumb here is to avoid touching the static members of another type from within a type constructor. Although the chances of seeing the previous scenario are slim, the effects would be difficult to debug and track down since there are few diagnostics available.

Static Reflection

The ability to examine the metadata of a type or object instance at run time is a powerful feature. Reflection enables you to build tools like object browsers and to create extensible applications with the ability to plug in types at run time. Using reflection techniques on static members of a type differs slightly from reflecting on instance members of an object. For example, the code in Figure 8 is going to get the value of the static property Message from the type Static1. It will also try to get the same property value using a Type reference for Static2.

Figure 8 Reflection on Static Members

using System; using System.Reflection; namespace Reflection { class Class1 { [STAThread] static void Main(string[] args) { Type type; object result; PropertyInfo[] properties; object instance = null; object[] index = null; type = typeof(Static1); properties = type.GetProperties( BindingFlags.Static | BindingFlags.Public); result = properties[0].GetValue(instance, index); Console.WriteLine(result.ToString()); type = typeof(Static2); properties = type.GetProperties( BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); result = properties[0].GetValue(instance, index); Console.WriteLine(result.ToString()); } } class Static1 { static public string Message { get { return message; } } static string message = "Hello World"; } class Static2 : Static1 {} }

When the code fetches the properties for the Static1 type using GetProperties, it passes binding flags to indicate it wants static members with public visibility. These flags are enough to get the metadata needed about the Message property in the array of PropertyInfo objects. Next, the GetValue method of the first PropertyInfo object in the array is invoked (in this case it is the PropertyInfo for the Message property). Typically, you need to pass an instance of an object as the first parameter to GetValue, but since this is a static member, there is no object to pass. Instead, you can use a null reference for the instance when working with a static property.

Finally, try to get the value of Message using the Type object for Static2. Static members are not inherited, even though IntelliSense® and the C# compiler will produce the illusion of inheritance, which I'll discuss later. To reach the Message property in this case, you need an additional binding flag, FlattenHierarchy, which tells the runtime to include base types in the search for static members.

Static Classes in the .NET Framework 2.0

The classes used so far have a couple of design flaws. Even though these classes have no instance members, you could still create instances of the classes using the new operator in C# or Visual Basic. In addition, someone could inherit from these classes, even though you never intended these classes to serve as base classes.

The solution to the inheritance problem is to apply the sealed keyword in C# or the NotInheritable keyword in Visual Basic. To prevent creation of the classes from code external to the class (although it could still be instantatied by a member of the class), you could add private default constructors to the classes. With C# 2.0, the solution to both of these problems is to apply the static keyword at the class level, as shown in Figure 9.

Figure 9 Static Classes in C# 2.0

using System; using System.Collections.Generic; using System.Text; namespace StaticClass { class Program { static void Main(string[] args) { Console.WriteLine(Static1.Message); // error: cannot declare variable of type Static1 Static1 s; // error: cannot create an instance of Static1 object o = new Static1(); } } static class Static1 { public static string Message { get { return _message; } } static string _message = "Hello World"; // error: cannot have an instance constructor Static1() { } } // error: cannot derive from a static class static class Static2 : Static1 { } }

The new syntax allows the compiler to enforce a number of new rules. You cannot declare a variable of type Static1 because it is marked as a static class. You also cannot derive from Static1, or add any non-static members to the class. Note also that it's an error to derive a static class from any class other than object.

When you upgrade to the .NET Framework 2.0, you should review your C# classes that have no instance methods. These classes will be candidates for using the static keyword on the class declaration. The additional compile time checks outlined earlier in this article will make the review worth the effort. In the current beta, Visual Basic does not support the Shared keyword on a class declaration. But Visual Basic still has some tricks not available in C#.

Static Locals

A little-known feature in Visual Basic is the support for static variables inside a Sub or Function. Static local variables retain their value between method calls. In this sense, they act just like a class-level variable, but their visibility is limited to a single method.

Static locals were available in versions of Visual Basic before .NET, so presumably this feature is present to simplify the porting of legacy code. Although C and C++ programmers are familiar with static local variables, C# did not carry this feature forward. The static local variable is interesting because the common language runtime (CLR) does not support static variables inside a method. Nevertheless, the code in Figure 10 will compile, execute, and produce the expected results by printing "Count = 1" to "Count = 10."

Figure 10 Visual Basic .NET Statics

Module Module1 Sub Main() Dim i As Integer Dim f As New Foo For i = 1 To 10 Console.WriteLine(f.GetMessage()) Next End Sub End Module Public Class Foo Public Function GetMessage() As String Static Dim i As Integer = 0 i = i + 1 Return "Count = " + i.ToString() End Function End Class

Since the CLR does not support static locals, the Visual Basic compiler must do additional work to make the program successful. The compiler adds two variables to the class with special names, as can be seen in the ILDASM in Figure 11. The first field in this example is an Integer field with the name of $STATIC$GetMessage$200E$i. This field will hold the value of the static local variable i in GetMessage. The second field, of type StaticLocalInitFlag, assists in initializing the variable i correctly the first time the method executes on each instance of class Foo. It is important to note these fields are not shared and that each instance of Foo will have a new local variable i to initialize and work with.

Figure 11 Visual Basic .NET Compiler Support for Statics

Initialization of a static local requires a bit of work. If you looked at the MSIL for the GetMessage method, you would find 62 instructions (compared to 13 instructions for a similar method that doesn't use only a class-level field). Most of these instructions are in place to check for the initialization of i, and to perform a thread-safe initialization using a lock.

Given the runtime overhead in using a static local variable, it would be wise for you to avoid the feature in current designs. If you have ported code from Visual Basic 6.0 to Visual Basic .NET with static local variables, you should definitely consider refactoring the variable into a class-level variable, which should be a relatively straightforward task.

Static Illusions

Static local variables are not the only compiler trick around. As I alluded to earlier, the CLR does not support the inheritance of static members. Nevertheless, both the Visual Basic and C# compilers allow you to touch static members of a base type through a derived type name. In addition, Visual Basic .NET allows you to touch a static member through an instance of a type, even a derived type, as you can see in the code in Figure 12.

Figure 12 Accessing Static Members from Instances

Module Module1 Sub Main() Console.WriteLine(Static2.Message) Dim s As New Static2 Console.WriteLine(s.Message) End Sub End Module Class Static1 Public Shared ReadOnly Property Message() As String Get Return "Hello World" End Get End Property End Class Class Static2 Inherits Static1 End Class

An examination of the MSIL for the Main method in Figure 12 will show that the Visual Basic compiler maps both statements with the Message property into a call to Static1::get_Message, completely circumventing the Static2 class. Even though you won't use the instance of Static2, the compiler will still create the new object so as not to miss any side effects of invoking the constructor.

Using a static member through an instance variable should be discouraged. In fact, the current beta of the Visual Basic compiler in Visual Studio® 2005 will produce the compiler warning, "Access of shared member through an instance." In C# it causes a compiler error if you use a static member of a class through an instance variable; however, you can still reach a static member through a derived type class name using the exact same trick that the Visual Basic compiler uses.

Static Conclusions

There are trade-offs to evaluate when adding static members to a type at both design time and run time. At design time, you need to carefully choose which members to mark as static and avoid losing an object-oriented design. Try not to lump diverse functionality into a class by creating a dumping ground for unrelated static methods. If a class contains only static members, remember to mark the class as static or seal the class (NotInheritable) and give the class a private constructor to avoid instance creation.

You should also remember that static classes that preserve state between method calls should be made thread safe by default. Making a class thread safe requires additional care during the implementation and testing of the class. Before going down this road, ask yourself if the extra overhead is absolutely necessary.

Thread safety also has performance implications at run time. In this article, you've seen some performance consequences when a class has a static constructor. Performance implications are particularly important to evaluate when you are writing code for a reusable library. You never know when someone might use your class in a worst-case scenario, like the one that is described at the beginning of this article.

Finally, document your static classes well. Shortcuts to common operations with static methods, such as the static methods Open and Copy on the System.IO.FileClass, will generally be well understood. As soon as you implement a nontrivial class maintaining state between method calls, consumers of the class will want to know details about thread safety and performance. The more you can say up front about a class, the less time you may spend troubleshooting problems later.

K. Scott Allen is the Chief Software Architect for Medisolv Inc. in Columbia, MD. Scott is also a founder of the .NET community site OdeToCode.com. You can reach Scott at scott@OdeToCode.com, or read his blog.