Question

I am intending to come from the perspective of development choices, code reviews, and general testing against defined environments (Development, Test, Production, etc.). I will be using C# as the primary coding reference.

What I am looking for:

  • Is there anything with the following code change that could have been recognized as a potential risk? (I don't know if this would have been considered a code smell or an identifiable risk during code reviews/testing)
  • The issue below appears to be environment specific, possible IIS/Server configuration differences. This item might be too broad, so I will remove it if that ends up being the case. Is there a way to identify if a web application is at risk of failing/having exceptions thrown due to a specific setting in IIS/Server?

Details:

I recently ran into a "type initializer 'X' threw an exception" error, after a change to the code, in a WebForms application with fairly poor logging. The class where the exception came from looks similar to the following:

public class someModelClass
{
    private static string someConnection = new someConnectionStringRequester().GetConnection(someParam1, someParam2, ...); // <- this was the primary change. someConnectionStringRequester is not a static class/doesn't have static methods
    //some public properties

    public static List<someModelClass> getInstancesOfModel(someParam1, someParam2, ...)
    {
        //return some list of models based on someConnection 
    }
}

There are a few other "model" classes that follow the same pattern with small varied difference.

In the initial testing (Development and Test environments), the app did not run into any exceptions. Once deployed to the Production environment, it started to bring up the exception. I did two things reviewed the code prior to the change and added some better logging. The class previously looked like:

public class someModelClass
{
    private static string someConnection = someOtherConnectionStringRequester.GetConnection(someParam1, someParam2, ...); // <- this was the original compared the the primary change above. someOtherConnectionStringRequester was a static class/had static methods
    //some public properties

    public static List<someModelClass> getInstancesOfModel(someParam1, someParam2, ...)
    {
        //return some list of models based on someConnection 
    }
}

After putting the logging in place, I had to force an exception to ensure that I would get a little more detail back. I made the GetConnection method "throw new Exception('testMessage')". As mentioned above, there are multiple "model" classes that look like this one but forcing the exception made a different class throw the type initializer exception, and this class gets called prior the the initial class that threw the exception. This different class looks like:

public class someDifferentModelClass
{
    private static string someConnection = new someConnectionStringRequester().GetConnection(someParam1, someParam2, ...); // <- this also had a similar change
    //some public properties


    public void someAction()
    {
        someConnectionStringRequester someOtherConnection = new someConnectionStringRequester();
        // do some work based on someOtherConnection 
        getInstancesOfModel(someParam1, someParam2, ...)
        // do some other work
    }

    private static List<someModelClass> getInstancesOfModel(someParam1, someParam2, ...)
    {
        //return some list of models based on someConnection
    }
}

This seems to point towards an issue when the class is first called, when the private static variable is called, or a mix of both.

Let me know if there is any information that needs to be clarified or added to make sense of the above content.

Was it helpful?

Solution

When you use static initialization, you give up any explicit control over the order of initialization. If things work, it's for one of the following reasons:

  • the static initialization only involves constant expressions.
  • you've carefully read the language specification, and have concluded that the initialization is guaranteed to work correctly.
  • things just happen to work by accident.

That the initialization used to work in your scenario is more of an accident, though it is in line with the language spec: the old class presumably did have a static constructor and thus did guarantee that its static fields were initialized prior to the constructor being called. Now, your class has no static constructor, so it's not guaranteed in which order different static variables will be initialized. For reference, see this question on Stack Overflow: Is the order of static class initialization in C# deterministic?

One possible solution is to defer the initialization until runtime, i.e. to establish the connection lazily the first time the property is accessed. This doesn't prevent all potential problems (e.g. it could lead to infinite recursion for a circular dependency), but it typically ensures a correct initialization order. Another problem with this approach is that errors won't become apparent until the property is accessed: we're deferring this code from initialization time to run time.

There is another reason why as much initialization as possible should be performed at runtime: your code (e.g. in a Main()) can perform setup prior to initialization. This also greatly aids debuggability.

Doing initialization correctly is a really difficult problem. I've learned my lesson to avoid static initialization after a few segfaults in C++, but C# suffers from exactly the same problems: any code that depends on a particular static initialization order is broken.

There are more elegant approaches to initialization available.

  • For example, a singleton might manage the lifetime of a connection (likely using lazy initialization).
  • The connection might also be provided by a dependency injection mechanism – possibly not involving any static variables at all.
  • In many cases, it is feasible to manage all dependencies by hand, e.g. to pass a database connection to the constructor of your model class.
  • Many architectures consider that the database connection should not be part of your data model, and outsource the connection to a repository object. However, that would involve a drastic change to your architecture and is probably not feasible.
Licensed under: CC-BY-SA with attribution
scroll top