11

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

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

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

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

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

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

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

4

5 回答 5

14

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

听起来您想要某种“模块或程序集初始化程序”。我认为IL 中不存在这样的事情(尽管我可能是错的),而且它绝对不存在于 C# 中。

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

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

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

为了在不强加任何东西的情况下使他人的生活更轻松,您始终可以自己包含该反射部分:

public static void RegisterAllParsers(Assembly assembly)

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

然后开发人员需要调用:

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

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

于 2013-03-22T18:56:51.693 回答
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();
}
于 2013-03-22T19:21:06.663 回答
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();         
    }

}

和另一个类库:

namespace ClassLibrary1
{
    public class SomeOtherClass
    {

    }

    public static class ModuleInitializerTest
    {
        public static void ModuleInitialize()
        {
            // Do interesting stuff here?
        }
    }
}
于 2013-03-22T19:50:10.903 回答
1

我强烈建议考虑为此(命名空间)使用托管可扩展性框架 (MEFSystem.ComponentModel.Composition )。然后,您的客户可以简单地添加一个[Export(typeof(ISomeParserInterface))]属性,MEF 将能够为您的解析器提供所有可用的扩展。

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

[Export(typeof(ISomeParserInterface))]
[ExportMetadata("ElementName", "SomeXmlElement")]
于 2013-04-05T21:39:32.997 回答
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 还负责许多其他事情,例如:

于 2013-04-05T22:13:27.443 回答