'Eager static constructor

I've read a few Stack Overflow questions and answers, along with some blog posts (including Jon Skeet's lazy singleton initialization), and they all seem to focus on making initialization as lazy as possible. It seems there are basically two options for static initialization:

  • At first reference to an instance or static member of a class
  • At an unspecified time between the start of the program and the first reference.

Is there any way to get a static constructor (or some form of initialization code) to run for a particular class (or classes) at the start of the program?

Context: Our library will be parsing incoming XML and return objects. The type of object returned depends on the XML element being parsed. We provide two simple classes: one is a very basic class that allows access to the attribues and inner XML (as a string), with no features; the second is for a specific type of object, and provides constraint checking and more context-specific names for accessing/editing values.

The parser determines how to parse a particular XML element by looking though its list of parsers. If it has a parser for the element it's parsing (determined by name), it uses that. If it doesn't, or if it fails, it falls back on the basic parser.

Developers using our library are highly likely to write their own classes for particular XML elements. Rather than having them manually add the parse method of each class to the list at the start of each application, it would be great if each class could have a static constructor that adds its own parser to the list, such that simply including the class in the project will register it. However, static constructors won't fire until the class is actually referenced, and we have no guarantee that every such class will be referenced before parsing begins.

Is there any way to guarantee some initializer fires for each of these classes at application start? The benefit of this would be simply including the classes in the project and not having to manually add each parse method to our parser's list at runtime, which is a fairly small convenience, so for the benefit to be worth the work, the solution needs to be pretty simple and straightforward to implement.



Solution 1:[1]

Afaik there is no way to do it explicitly, but you could create something like the following (I warn you now, its ugly and not fast):

[System.AttributeUsage(System.AttributeTargets.Class |
                   System.AttributeTargets.Struct)]
public class AppInitialized : System.Attribute
{
    private MethodInfo _mInfo;

    public AppInitialized(Type t, String method)
    {
        _mInfo = t.GetMethod(method, BindingFlags.Static | BindingFlags.Public);
    }

    public void Initialize()
    {
        if (_mInfo != null)
            _mInfo.Invoke(null, null);
    }
}

[AppInitialized(typeof(InitializeMe), "Initialize")]
public class InitializeMe
{
    public static void Initialize()
    {
        Console.WriteLine("InitializeMe initialized");
    }
}

And then when your application loads, use something like this to initialize everything with the custom attribute:

foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
    var a = Attribute.GetCustomAttribute(type, typeof(AppInitialized), true) 
        as AppInitialized;
    if (a != null)
        a.Initialize();
}

Solution 2:[2]

A bit like @FlyingStreudel, I too have cobbled together something that "kinda" does what you are after:

The Attribute:

[AttributeUsage(AttributeTargets.All)]
public class ModuleInitializerAttribute : Attribute
{
    private readonly string _assemblyName;
    private readonly Func<Module, bool> _modulePredicate;

    private readonly string _typeName;
    private readonly string _methodName;

    /// <summary>
    /// Only used in my test rig so I can make sure this assembly is loaded
    /// </summary>
    public static void CallMe() {}

    public ModuleInitializerAttribute(string assemblyName, string moduleName, string typeWithMethod, string methodToInvoke)
    {
        _assemblyName = assemblyName;
        _modulePredicate = mod => moduleName == null || mod.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase);
        _typeName = typeWithMethod;
        _methodName = methodToInvoke;

        AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
        AppDomain.CurrentDomain.DomainUnload += AppDomainUnloading;

        CheckLoadedAssemblies();
    }

    private void CheckLoadedAssemblies()
    {
        AppDomain.CurrentDomain.GetAssemblies().ToList().ForEach(this.CheckAssembly);
    }

    private void AppDomainUnloading(object sender, EventArgs e)
    {
        // Unwire ourselves
        AppDomain.CurrentDomain.AssemblyLoad -= this.OnAssemblyLoad;
        AppDomain.CurrentDomain.DomainUnload -= AppDomainUnloading;
    }

    private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
        CheckAssembly(args.LoadedAssembly);
    }

    private void CheckAssembly(Assembly asm)
    {
        if (asm.FullName == _assemblyName)
        {
            var module = asm.GetModules().FirstOrDefault(_modulePredicate);
            if (module != null)
            {
                var type = module.GetType(string.Concat(asm.GetName().Name, ".", _typeName));
                if (type != null)
                {
                    var method = type.GetMethod(_methodName);
                    if (method != null)
                    {
                        method.Invoke(null, null);
                    }
                }
            }
        }
    }

}

The Testing rig:

class Program
{
    [ModuleInitializer("ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "ClassLibrary1.dll", "ModuleInitializerTest", "ModuleInitialize")]
    static void Main(string[] args)
    {
        Console.WriteLine("Loaded assemblies:");
        var asms = AppDomain.CurrentDomain.GetAssemblies();
        foreach (var assembly in asms)
        {
            Console.WriteLine("\tAssembly Name:{0}", assembly.GetName());
            var mods = assembly.GetModules();
            foreach (var module in mods)
            {
                Console.WriteLine("\t\tModule Name:{0}", module.Name);
            }
        }
        // This should trigger the load of the ClassLibrary1 assembly
        aReference();
        Console.ReadLine();
    }

    static void aReference()
    {
        var foo = new SomeOtherClass();         
    }

}

And the other class lib:

namespace ClassLibrary1
{
    public class SomeOtherClass
    {

    }

    public static class ModuleInitializerTest
    {
        public static void ModuleInitialize()
        {
            // Do interesting stuff here?
        }
    }
}

Solution 3:[3]

I highly recommend considering the use of the Managed Extensibility Framework (MEF) for this (System.ComponentModel.Composition namespace). Your clients can then simply add an [Export(typeof(ISomeParserInterface))] attribute, and MEF will be able to provide your parser with all of the available extensions.

You can even use the ExportMetadataAttribute to allow your code to only instantiate the parsers it actually needs for the elements it encounters.

[Export(typeof(ISomeParserInterface))]
[ExportMetadata("ElementName", "SomeXmlElement")]

Solution 4:[4]

You could determine which parser to use for specific XML element based on a current context of XML parsing. Every CLR object which will be parsed from XML will be contained in some other CLR object as its member (field or property) except for the root object. So XML parser can be determined by member type (field type or property type). For root CLR object, to which XML must be parsed into, type has to be specified explicitly.

Here is longer C# sample code:

XmlParserLibrary project:

using System;
using System.Collections.Generic;
using System.Xml;

namespace XmlParserLibrary
{
    public sealed class XmlParser
    {
        private readonly IDictionary<Type, IXmlParser> parsers = new Dictionary<Type, IXmlParser>()
        {
            { typeof(string), new StringXmlParser() }
        };

        public T Parse<T>(XmlReader reader)
        {
            return (T)this.Parse(reader, typeof(T));
        }

        public object Parse(XmlReader reader, Type type)
        {
            // Position on element.
            while (reader.Read() && reader.NodeType != XmlNodeType.Element) ;

            return GetParser(type).Parse(reader);
        }

        private IXmlParser GetParser(Type type)
        {
            IXmlParser xmlParser;
            if (!this.parsers.TryGetValue(type, out xmlParser))
                this.parsers.Add(type, xmlParser = this.CreateParser(type));

            return xmlParser;
        }

        private IXmlParser CreateParser(Type type)
        {
            var xmlParserAttribute = Attribute.GetCustomAttribute(type, typeof(XmlParserAttribute)) as XmlParserAttribute;
            return xmlParserAttribute != null ? Activator.CreateInstance(xmlParserAttribute.XmlParserType) as IXmlParser : new FallbackXmlParser(this, type);
        }
    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
    public sealed class XmlParserAttribute : Attribute
    {
        public Type XmlParserType { get; private set; }

        public XmlParserAttribute(Type xmlParserType)
        {
            this.XmlParserType = xmlParserType;
        }
    }

    public interface IXmlParser
    {
        object Parse(XmlReader reader);
    }

    internal sealed class StringXmlParser : IXmlParser
    {
        public object Parse(XmlReader reader)
        {
            return reader.ReadElementContentAsString();
        }
    }

    internal sealed class FallbackXmlParser : IXmlParser
    {
        private readonly XmlParser xmlParser;
        private readonly Type type;

        public FallbackXmlParser(XmlParser xmlParser, Type type)
        {
            this.xmlParser = xmlParser;
            this.type = type;
        }

        public object Parse(XmlReader reader)
        {
            var item = Activator.CreateInstance(this.type);

            while (reader.Read())
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        var propertyInfo = this.type.GetProperty(reader.LocalName);
                        var propertyValue = this.xmlParser.Parse(reader.ReadSubtree(), propertyInfo.PropertyType);
                        propertyInfo.SetValue(item, propertyValue, null);
                        break;
                }

            return item;
        }
    }
}

XmlParserLibraryTest project:

using System.Xml;
using XmlParserLibrary;

namespace XmlParserLibraryTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var xmlParser = new XmlParser();

            Letter letter;
            using (var reader = XmlReader.Create("Letter.xml"))
                letter = xmlParser.Parse<Letter>(reader);
        }
    }

    public class Letter
    {
        public LetterAssociate Sender { get; set; }
        public LetterAssociate Receiver { get; set; }
        public LetterContent Content { get; set; }
    }

    public class LetterAssociate
    {
        public string Name { get; set; }
        public string Address { get; set; }
    }

    [XmlParser(typeof(LetterContentXmlParser))]
    public class LetterContent
    {
        public string Header { get; set; }
        public string Body { get; set; }
    }

    internal class LetterContentXmlParser : IXmlParser
    {
        public object Parse(XmlReader reader)
        {
            var content = new LetterContent();

            while (reader.Read())
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        switch (reader.LocalName)
                        {
                            case "Header":
                                content.Header = reader.ReadElementContentAsString();
                                break;
                            case "Body":
                                content.Body = reader.ReadElementContentAsString();
                                break;
                        }
                        break;
                }

            return content;
        }
    }
}

Letter.xml file:

<?xml version="1.0" encoding="utf-8" ?>
<Letter>
  <Sender>
    <Name>Sender name</Name>
    <Address>Sender address</Address>
  </Sender>
  <Receiver>
    <Name>Receiver name</Name>
    <Address>Receiver address</Address>
  </Receiver>
  <Content>
    <Header>This is letter header.</Header>
    <Body>This is letter body.</Body>
  </Content>
</Letter>

This is similar how XmlSerializer works, except it doesn't use reflection directly when parsing but before parsing to generate all parsers in a separate temporary assembly (before .NET 4.5). XmlSerializer also takes care of a lot of other things, for example:

Solution 5:[5]

To answer the question directly,

Is there any way to get a static constructor (or some form of initialization code) to run for a particular class (or classes) at the start of the program?

Yes, if you know the Type you can use RuntimeHelpers.RunClassConstructor().

For example, in a generic method

CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);

or via a Type variable

CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);

This has been part of the Framework since 1.1 and still exists through .NET 6, so I'm not sure why nobody else mentioned it.

As an example, I've used this to force the class constructor to run when using the curiously recurring template pattern as part of a non-enum enumeration types, where you need the derived classes class constructors to run to initialize the enum values. Extra-lazy static initialization added in .NET 4 causes derived types to delay their class constructors, even past when generic conversion operators are invoked on the base class.

By invoking RunClassConstructor in the base class's static constructor, you can force the runtime to also run the class constructor for the derived type T.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 JerKimball
Solution 3 Sam Harwell
Solution 4 Stipo
Solution 5