Search code examples
c#.netinitializationautomapperstatic-constructor

static constructor not invoked


I'm trying to use the AutoMapper for model-viewmodel mapping and wanted to have the mapping configuration executed once in the static constructor of the type. The static constructor of a type is not invoked when Mapper.Map (AutoMapper) is invoked with that type.

My understanding is that the Mapper.Map would try to access the type, its members through reflection and on the first attempt of the usage the static constructor would be called. This is something basic but challenges my understanding. The code snippet is provided.

class SampleViewModel 
{
    static SampleViewModel()
    {
        Mapper.Initialize(cfg => cfg.CreateMap<Sample, SampleViewModel>().ReverseMap());
    }

    public SampleViewModel()
    {
    }


    public int PropertyA { get; set; }
    public int PropertyB { get; set; }
}


 Sample s = new Sample { PropertyA = 10, PropertyB = 20 };
 var obj = Mapper.Map<SampleViewModel>(s); // fails

Isn't the static constructor called (if provided) when the type and members are accessed through reflection for the first time?


Solution

  • You're not accessing any members of SampleViewModel - it's not enough to just reference the type itself.

    Mapper.Map only accesses its own internal "dictionary" of mappings - before it could ever get to a point where it deals with a SampleViewModel, it fails. The static constructor never runs, so it cannot add "itself" into the Mapper.

    Now, if this didn't involve reflection, you would be right that the static constructor would be called - simply because it would happen during the compilation of the method containing the access, e.g.:

    var obj = Mapper.Map<SampleViewModel>(s);
    Console.WriteLine(obj.SomeField);
    

    In this case, since the method is referencing a field on SampleViewModel, the static constructor for SampleViewModel will be invoked during JIT compilation of the containing method, and as such, the Mapper.Map<SampleViewModel>(s) line will execute correctly, since the mapping is now present. Needless to say this is not the proper solution to your problem. It would just make the code absolutely horrible to maintain :)

    DISCLAIMER: Even though this might fix the problem right now, it depends on a non-contractual behaviour in the current implementation of MS.NET on Windows. The contract specifies that the type initializer is invoked before any access to a member of the type, but that still means that a valid implementation of CIL might only call the type initializer after Mapper.Map, as long as it happens before obj.SomeField - and even then, it might be that obj.SomeField gets optimized away if the compiler can ensure it is safe to do so. The only real way to enforce the call of the type initializer is to call RuntimeHelpers.RunClassConstructor, but by that point, you could just as well add a static Init method or something.

    The real problem is that you shouldn't really initialize stuff like this in a static constructor in the first place. Mappings should be set in some kind of deterministic initialization process, say, an explicitly invoked InitMappings method. Otherwise you're opening yourself to a huge can of Heisenbugs, not to mention subtle changes in the CLR breaking your whole application for no apparent reason.

    Static constructors just aren't meant for "registration", just the initialization of the type itself - anything else is an abuse, and will cause you (or the .NET compatibility team) trouble.