C# 渴望静态构造函数

C# 渴望静态构造函数,c#,initialization,C#,Initialization,我已经阅读了一些关于堆栈溢出的问题和答案,以及一些博客文章(包括Jon Skeet的lazy singleton initialization),它们似乎都专注于使初始化尽可能地懒惰。静态初始化基本上有两种选择: 第一次引用类的实例或静态成员时 在程序开始和第一次引用之间的未指定时间 有没有办法让静态构造函数(或某种形式的初始化代码)在程序开始时为特定类运行 上下文:我们的库将解析传入的XML和返回对象。返回的对象类型取决于正在解析的XML元素。我们提供了两个简单的类:一个是非常基本的类,允

我已经阅读了一些关于堆栈溢出的问题和答案,以及一些博客文章(包括Jon Skeet的lazy singleton initialization),它们似乎都专注于使初始化尽可能地懒惰。静态初始化基本上有两种选择:

  • 第一次引用类的实例或静态成员时
  • 在程序开始和第一次引用之间的未指定时间
有没有办法让静态构造函数(或某种形式的初始化代码)在程序开始时为特定类运行

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

解析器通过查看特定XML元素的解析器列表来确定如何解析该元素。如果它对正在解析的元素有一个解析器(由名称决定),它将使用该解析器。如果没有,或者如果失败了,它就求助于基本解析器

使用我们的库的开发人员很可能为特定的XML元素编写自己的类。与其让他们在每个应用程序开始时手动将每个类的解析方法添加到列表中,不如让每个类都有一个静态构造函数,将自己的解析器添加到列表中,这样只需将类包含在项目中就可以注册它。然而,静态构造函数在类被实际引用之前是不会激发的,并且我们不能保证在解析开始之前每个这样的类都会被引用

是否有任何方法可以保证在应用程序启动时为这些类中的每个类触发一些初始值设定项?这样做的好处是只需将类包含在项目中,而不必在运行时手动将每个解析方法添加到解析器的列表中,这是一个相当小的方便,因此,为了使这项工作值得一做,解决方案需要非常简单且易于实现

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


听起来您需要某种“模块或程序集初始值设定项”。我不认为这样的东西存在于IL中(尽管我可能错了),而且在C#中肯定不存在

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

编辑:我找到了更多的选项:

  • 创建一个初始值设定项,该初始值设定项查找
    ModuleInitializer。使用Mono-Cecil在任何命名空间中运行
编辑:有了更多的背景,我怀疑任何治疗方法都会比疾病更糟糕。任何想要编写基于反射的“查找具有此属性的所有解析器”(或类似内容)的开发人员都没有太多工作要做,但我认为您不想干扰他们自己的应用程序启动

为了让别人的生活更轻松,而不强加任何东西,你可以始终包括自己的反思部分:

public static void RegisterAllParsers(Assembly assembly)
。。。这可能是基于属性的。当然,它只能明智地选择静态解析方法——如果任何开发人员有一个工厂,它可以根据工厂的初始化以不同的方式进行解析,那么您就无法轻松地自动注册它

然后,开发人员需要调用:

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

启动时。要记住这一点可能不太难——而且大多数应用程序只有一个入口点,或者至少有一些通用的启动代码。

Afaik没有明确的方法,但是您可以创建如下内容(我现在警告您,这很难看,也不快):

然后,当您的应用程序加载时,使用类似这样的方法使用自定义属性初始化所有内容:

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

有点像@Flyingstrudel,我也拼凑了一些“有点”符合你的要求的东西:

属性:

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

}
另一个类库:

namespace ClassLibrary1
{
    public class SomeOtherClass
    {

    }

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

我强烈建议为此(
System.ComponentModel.Composition
namespace)考虑使用。然后,您的客户端只需添加一个
[Export(typeof(isomparserinterface))]
属性,MEF就可以为您的解析器提供所有可用的扩展

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

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

您可以根据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;
        }
    }
}
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;
        }
    }
}
<?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>
使用系统;
使用System.Collections.Generic;
使用System.Xml;
命名空间XmlParserLibrary
{
公共密封类XmlParser
{
专用只读IDictionary解析器=新字典()
{
{typeof(string),新的StringXmlParser()}
};
公共T解析(XmlReader)
{
返回(T)this.Parse(reader,typeof(T));
}
公共对象解析(XmlReader,类型)
{
//元素上的位置。
while(reader.Read()&&reader.NodeType!=XmlN
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;
        }
    }
}
<?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>