1

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.

1
  • it ended up being a "object not set to an instance of an object" exception. I agree with you, and that is being included in the fix. The point of the question was that I think it is now noticeable what "should" have been done, but I don't know what in code review/testing "should" have pointed out that static could cause an issue. Commented Dec 21, 2020 at 20:20

1 Answer 1

2

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.
3
  • Thank you for the very detailed response. Is there a way to force an initialization order change as a test? Just trying to keep with the idea of what could have been done in code review/testing that might have uncovered the "things just happened to work accidentally" scenario. Commented Dec 21, 2020 at 20:28
  • 1
    Yes, by explicitly initializing static members in your static constructor(s). Commented Dec 21, 2020 at 20:47
  • @RobertHarvey - Thank you for that. Commented Dec 21, 2020 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.