渴望静态构造函数

时间:2013-03-22 18:52:49

标签: c# initialization

我已经阅读了一些Stack Overflow问题和答案,以及一些博客文章(包括Jon Skeet的懒惰单例初始化),他们似乎都专注于尽可能地使初始化变得懒惰。似乎静态初始化基本上有两个选项:

  • 首先引用类的实例或静态成员
  • 在程序开始和第一次引用之间的未指定时间。

是否有任何方法可以在程序开头为特定类(或类)运行静态构造函数(或某种形式的初始化代码)

上下文:我们的库将解析传入的XML并返回对象。返回的对象类型取决于要解析的XML元素。我们提供了两个简单的类:一个是非常基本的类,允许访问属性和内部XML(作为字符串),没有任何功能;第二个是针对特定类型的对象,并提供约束检查和更多特定于上下文的名称,用于访问/编辑值。

解析器通过查看解析器列表来确定如何解析特定的XML元素。如果它有解析的元素的解析器(由名称确定),它使用它。如果它没有,或者它失败了,它将回退到基本的解析器上。

使用我们的库的开发人员很可能为特定的XML元素编写自己的类。如果每个类都有一个静态构造函数可以将自己的解析器添加到列表中,而不是让它们手动将每个类的parse方法添加到每个应用程序开头的列表中,那么只需将类包含在该项目将进行注册。但是,在实际引用类之前,静态构造函数不会触发,并且我们无法保证在解析开始之前将引用每个此类。

在应用程序启动时,有没有办法保证每个类的初始化程序都会触发?这样做的好处就是简单地包含项目中的类,而不必在运行时手动将每个解析方法添加到我们的解析器列表中,这是一个相当小的便利,因此为了使工作更有价值,解决方案需要实施非常简单明了。

5 个答案:

答案 0 :(得分:14)

  

有没有办法在程序开始时为特定的类(或类)运行静态构造函数(或某种形式的初始化代码)?

听起来你想要某种“模块或汇编初始化器”。我不认为这样的东西存在于IL中(尽管我可能是错的)并且它绝对不存在于C#中。

您始终可以创建某种属性,然后使用反射来查找使用该属性修饰的所有类型,并显式初始化它们。 (请注意,使用泛型类型会变得更加棘手......您可能希望将其限制为非泛型类型。)

编辑:我发现了更多选择:

编辑:有了更多的背景,我怀疑任何治疗都会比疾病更糟糕。任何想要编写基于反射的“查找具有此属性的所有解析器”(或类似)的开发人员都没有太多工作要做,但我认为您不想干扰他们自己的应用程序启动。

为了让别人的生活更轻松而不强加任何东西,你总是可以自己包含那个反射部分:

public static void RegisterAllParsers(Assembly assembly)

......这可能是基于属性的。它当然只能合理地选择静态解析方法 - 如果任何开发人员有一个工厂可以根据工厂的初始化以不同的方式解析,你就不能轻易地自动注册。

然后开发人员需要致电:

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

启动时。这可能不是很难记住 - 大多数应用程序只有一个入口点,或者至少有一些常见的启动代码。

答案 1 :(得分:2)

Afaik没有办法明确地做到这一点,但你可以创建类似下面的东西(我现在警告你,它丑陋但不快):

[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");
    }
}

然后当你的应用程序加载时,使用类似的东西用自定义属性初始化所有内容:

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

答案 2 :(得分:2)

有点像@FlyingStreudel,我也拼凑了一些“有点”的东西:

属性:

[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);
                    }
                }
            }
        }
    }

}

测试台:

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();         
    }

}

另一类lib:

namespace ClassLibrary1
{
    public class SomeOtherClass
    {

    }

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

答案 3 :(得分:1)

我强烈建议考虑将Managed Extensibility Framework (MEF)用于此(System.ComponentModel.Composition命名空间)。然后,您的客户只需添加[Export(typeof(ISomeParserInterface))]属性,MEF就能为您的解析器提供所有可用的扩展。

您甚至可以使用ExportMetadataAttribute来允许您的代码仅实例化它遇到的元素实际需要的解析器。

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

答案 4 :(得分:0)

您可以根据XML解析的当前上下文确定要用于特定XML元素的解析器。 将从XML解析的每个CLR对象将作为其成员(字段或属性)包含在除根对象之外的其他CLR对象中。因此,XML解析器可以由成员类型(字段类型或属性类型)确定。 对于必须将XML解析为的根CLR对象,必须明确指定类型。

这是更长的C#示例代码:

XmlParserLibrary项目:

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项目:

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文件:

<?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>

这与XmlSerializer的工作方式类似,不同之处在于它在解析时不会直接使用反射,而是在解析之前在单独的临时程​​序集中生成所有解析器(在.NET 4.5之前)。 XmlSerializer还会处理很多其他事情,例如: