Subtle .NET Static Reference Type Gotcha

Published on
Reading time
Authors

One of the fundamental concepts that any developer has to understand when developing .NET solutions is the difference between Reference and Value types.  There are a lot of technical discussions online already about the differences between the two but the key concept to pull out of the MSDN description on Reference types is:

"Variables of reference types, referred to as objects, store references to the actual data."

So, rather than hold the data directly, a Reference type holds a reference to where the data actually is.

/* Value Type */
int someValue = 2;

// someOtherValue holds 2
int someOtherValue = someValue;

/* Reference Type */
MyCustomClass myClassOne = new MyCustomClass();

// myClassTwo holds a reference to myClassOne
MyCustomClass myClassTwo = myClassOne;

Too Much Static

The static keyword will be one that you will come across and use.  A good description of how it affects classes and members can be found on MSDN.  One of the justifications for using static declarations  is around performance - "...a static class remains in memory for the lifetime of the application domain in which your program resides".

Because of the persistent nature of static variables you will see them used as a poor man's equivalent to the Application store in ASP.Net.  While quick and easy this approach does have potential to cause issues - especially if you are unaware of the duration of the application domain lifetime and on how you utilise your static variable.  What do I mean by this?

Firstly, think about the application domain for your web application... that's right, it's the Application Pool that hosts your web app.

Secondly, think about the users of the web application... that's right, they may be many different people using the code over many requests - all being served by one long running Application Pool.

So what?  Let us consider the following example.

// ContactList class
public static class ContactList {
    public static List<FormEmail> Emails { get; private set; }

    public static ContactList() {
        Emails = SomeMethodToLoadEmails(); }
    }

Now to use this class in an ASP.Net Application thus.

// Prepare Email List For Sending
public PrepareEmail(FormType type, AdditionalLocation additionalLocation)
{
    FormEmail mailToList = ContactList.Emails.Find(x => x.EmailFormType == type);

    // If this is for NSW we need to add another email
    if(additionalLocation.Location == Location.NSW) {
        mailToList.Recipients.Add(additionalLocation.MailOption);
    }
    //...
}

But wait! What happens next time we run this code? Can you spot the problem?

The mailToList variable holds a reference to the ContactList Emails list object which is declared as static. When we call to 'Add' at line 9 in the last code sample we will actually add the entry to the original ContactList Emails list.

The result is that until the Application Pool is restarted the ContactList Emails List will continue to be added to which is not what is intended.

A Corrected Way

There are a couple of ways to resolve this issue.  One would be to remove the static nature of the ContactList class and its members, but that may not be the best way to go.

Depending on the object causing the issue you may be able to leverage one of the Copy() or Clone() methods provided by the .Net Framework.  Note that for custom classes you write you will need to provide your own implementation of the Copy() method.

A simple way to resolve the above is to modify the offending code as follows.

// Prepare Email List For Sending
public PrepareEmail(FormType type, AdditionalLocation additionalLocation)
{
    FormEmail mailToList = ContactList.Emails.Find(x => x.EmailFormType == type);

    // Create new local non-static variable and assign values from static matches.
    FormEmail localList = new FormEmail
                            {  
                                Subject = mailToList.Subject,
                                Template = mailToList.Template
                            };

    // add all recipients from static instance to local one
    localList.Recipients.AddRange(mailToList.Recipients);

    // If this is for NSW we need to add another email
    if(additionalLocation.Location == Location.NSW)
    {
        localList.Recipients.Add(additionalLocation.MailOption);
    }
    //...
}

So there you go, a very subtle issue in using static reference types in your ASP.Net (and WinForms, etc.) projects and how you can go about fixing it.

Hope this saves you some time.