Eager static constructor
Asked Answered
S

6

11

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.

Splenetic answered 22/3, 2013 at 18:52 Comment(1)
I think the below suggestions of reflecting on distinguishable classes (e.g. by attributes, base class or interface) is the most promising. You could put the reflecting code some static constructor of a class that surely is initialized.Granada
S
14

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?

It sounds like you want some sort of "module or assembly initializer". I don't think such a thing exists in IL (although I could be wrong) and it definitely doesn't exist in C#.

You could always create some sort of attribute and then use reflection to find all the types decorated with that attribute, and initialize them explicitly. (Note that it becomes trickier with generic types... you'd probably want to limit it to non-generic ones.)

EDIT: I've found a couple more options:

EDIT: With more context, I suspect any cure would be worse than the disease, as it were. Any developer who wants to write a reflection-based "find all parsers with this attribute" (or similar) doesn't have very much work to do, but I don't think you want to interfere with their own application start-up.

To make others' lives easier without imposing anything, you could always include that reflection part yourself:

public static void RegisterAllParsers(Assembly assembly)

... which would probably be attribute-based. It could only sensibly pick up static parse methods, of course - if any developer had a factory which could parse in different ways depending on initialization of the factory, you couldn't easily register that automatically.

The developer would then need to call:

LibraryClass.RegisterAllParsers(typeof(SomeTypeInProgram).Assembly);

on start-up. That's probably not too hard to remember to do - and most applications only have a single entry point, or at least some common start-up code.

Schrock answered 22/3, 2013 at 18:56 Comment(21)
"module or assembly initializer" sounds pretty accurate. Basically we want a couple classes to handle some initialization on their own, rather than developers using the library having to initialize them by hand in every app. It's a matter of convenience/simplicity, so if it's as terribly complicated as you suggest, it probably isn't worth it.Splenetic
And the scan-the-assembly-looking-for-the-attribute method is going to be expensive because (1) it involves reflection and (2) it's going to scan every type in the assembly.Thistly
@Jim: I've found a couple more options for you - see my edit.Schrock
@Thistly Define 'expensive'. I do a reflection scan for attributes in one of my current projects to locate and register processing classes based on attributes and method signatures. 30 lines of code that I wrote once, isn't particularly efficient (uses Linq for instance)... and executes in ~11ms on average, one time each time I run the application. All I need is one attribute tagged on a class instead of having to manage a list manually. I don't count that as an expensive thing.Acquiesce
@Corey: I completely agree: in most cases, I strongly suspect the performance aspect is irrelevant. It's inconvenient to have to call it in some cases though, in which case the other options listed in my edit are handy.Schrock
@JonSkeet It would be nice to have an atstart mechanism built in to the framework that handled this for us, or some way to tag static constructors for execution during startup, but the methods you listed are good workarounds for their lack :)Acquiesce
@Acquiesce "Expensive" as in "will take several orders of magnitude more time (how many depending on the number of types in the checked assemblies) than directly calling initialization functions."Thistly
@cdhowie: Sure, but this only needs to happen once - I'd be quite surprised if it took even a second on a pretty large assembly. For many apps - and almost anything server-side - that would be fine.Schrock
@JonSkeet Perhaps, but nonetheless it's something to be aware of. I'm not saying that it's bad to spend cycles on this, just that it's good to know that those cycles will be spent.Thistly
@Thistly It took me about 10 minutes to write from scratch, and each of my processing classes just needs a single attribute to add to the list of processors. If I want to disable a processor for testing, I comment the attribute on the processor I'm disabling, etc. From a development viewpoint, this is a much more efficient way for me to do things. It's a matter of viewpoint I guess. I just have a bad reaction to people talking about 'expensive' one-shot processes.Acquiesce
@Jim: Have you investigated the projects listed in my edit? It's odd to start a bounty but then stay silent :)Schrock
@JonSkeet As far as I can tell, the two options you've linked to provide a way to tell your dll to run one specific initialization method at startup. We were looking for something that could be stuck into any class (including third-party extensions of our code) that can do immediate initialization.Splenetic
@Jim: Well that code will be run on assembly load (I believe). In the first comment you specified: '"module or assembly initializer" sounds pretty accurate' - and that's what those last two options provide. You could use that combined with the reflection approach to find all the types which have a particular attribute, for example - then you don't need to explicitly call anything. Or it's feasible that you could adapt the Cecil-based tool to rewrite the IL to automatically include those calls directly, to avoid reflection. It's hard to know exactly what more you're looking for.Schrock
@JonSkeet What I'm looking for is a simpler way of acheiving this, if possible. The payoff we will get for doing this is a minor convenience to developers using our library. It's not worth implementing something with that kind of complexity for the minor benefit. If there were something simple enough that it would be worth doing, that would be great, but it's looking like it's just not worth it for our case.Splenetic
@Jim: It's not really clear how the library part fits in, to be honest. What does your library do, and why would other developers using it have unusual static initialization needs?Schrock
This particular feature parses XML into custom objects. We've got a generic base class that provides minimal functionality (direct access to attributes, inner XML as a string), and two simple classes that provide specific functionality based on the type of object represented by the XML. Our parser maintains a list of parsers for specific types, and uses those instead of the basic one when certain elements are being parsed (determined by name). We were hoping that any new parsers written for custom object types could register at load time instead of developers needing to manually do it for eachSplenetic
A static constructor could work to register the class's parser, except that there is no guarantee it will run before anything gets parsed (since there is no guarantee the class will be referenced before then).Splenetic
@Jim: Right - that context around parsing was definitely missing, and helps anyone trying to answer usefully. In particular, it sounds like you want your library to trigger initialization in a different library, which changes things somewhat too... you probably wouldn't want your developers to have to use Fody etc.Schrock
@JonSkeet I've updated the question; hopefully the additional context/explanation makes it a bit clearer.Splenetic
@Jim: See my edit. I think the fact that it's someone else's application that you want to change the initialization of makes it less compelling.Schrock
@JonSkeet Your "cure worse than disease" line pretty much sums it up; this doesn't seem worth it for the tiny convenience, especially since they'd have to call a register method anyways.Splenetic
P
2

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();
}
Pistoleer answered 22/3, 2013 at 19:21 Comment(0)
D
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?
        }
    }
}
Diacritic answered 22/3, 2013 at 19:50 Comment(0)
S
2

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")]
Sparling answered 5/4, 2013 at 21:39 Comment(0)
S
0

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:

Slr answered 5/4, 2013 at 22:13 Comment(0)
G
0

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.

Groundless answered 2/5, 2022 at 19:23 Comment(1)
Interesting, although I don't think this would've solved the problem. Primarily because we wouldn't know the type ahead of time, and because the types we needed to initialize wouldn't necessarily share a base class (can't remember whether they did, but I doubt it)Splenetic

© 2022 - 2024 — McMap. All rights reserved.