Can a Static Field Be Initialized Multiple Times?

December 12th 2015 C# Universal Apps

The Plot

Take a look at the following code:

public static class StaticClass
{
    private static readonly Lazy<string> Lazy = 
        new Lazy<string>(() => DateTime.Now.ToString());

    public static string Singleton => Lazy.Value;
}

Under what circumstances can the static field Lazy be initialized multiple times, causing the Singleton property to return different values? C# language specification makes it pretty clear that static fields are initialized only once, before the class is first used:

The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class.

I already knew that when I was recently involved in troubleshooting an issue caused by the lazy factory function being invoked multiple times. This meant, it was time for some creative thinking. Below are the potential causes we thought off in the process of resolving the issue.

Multiple Application Domains

Code running in different application domains is isolated from each other, hence the same static class will live independently in each application domain. It will need to get initialized in each application domain separately. This could be a reason for the factory method to be run multiple times. However, it couldn't have happened in our case. We were developing a universal application which doesn't support multiple application domains.

Reflection

Although the static field is marked as readonly, this doesn't completely prevent its value from changing. The following code utilizing reflection could be used to reset the field value:

public void ResetField()
{
    var lazyField = typeof(StaticClass).GetTypeInfo().DeclaredFields
        .First(field => field.Name == "Lazy");
    lazyField.SetValue(null, 
        new Lazy<string>(() => DateTime.Now.ToString()));
}

Well, there was no such rogue code in our application. We had to look for another reason.

Shared Files Across Projects

In the end it turned out, there were actually two different static classes involved, all along. We just didn't notice it. How could that happen?

The static class was located in a portable class library and referenced from multiple assemblies. We were in the middle of porting a Universal Windows 8 application to Windows 10. Of course, we didn't convert all the projects involved at the same time. Windows 10 universal projects can reference Universal Windows 8 assemblies, which allowed us to first convert the application and work our way down the dependencies.

Since we will keep maintaining the Windows 8 application, we couldn't just upgrade the class library containing our static class. Instead, we decided to create a new Windows 10 library, which could take advantage of the new APIs and share the existing files with the old portable class library. We ended up with the following dependencies:

Assembly dependencies in our solution

The static class was present both in SharedLibrary.PCL and SharedLibrary.UWP assemblies. It even had the same namespace since the code files were shared. Still, the class used directly from App.UWP was a different class from the one used indirectly via Component.Win8. Each one of them was initialized separately, therefore the factory method was of course called twice.

Since the code file was shared, there was only one document tab opened in Visual Studio. Even during debugging, this made it difficult to notice that two different classes were involved. The same breakpoints worked for both classes. Even the context switcher in the top left corner of the editor didn't show the correct value, when the execution stopped at a breakpoint.

Context switcher in the editor window

Once we finally determined that there were actually two classes, not one, fixing the issue wasn't a problem any more.

Copyright
Creative Commons License