11

いくつかのスタック オーバーフローの質問と回答、およびいくつかのブログ投稿 (Jon Skeet の遅延シングルトン初期化を含む) を読みましたが、それらはすべて初期化を可能な限り遅延させることに重点を置いているようです。静的初期化には基本的に 2 つのオプションがあるようです。

  • クラスのインスタンスまたは静的メンバーへの最初の参照
  • 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.

4

5 に答える 5

14

プログラムの開始時に特定のクラス (またはクラス) に対して実行する静的コンストラクター (または何らかの形式の初期化コード) を取得する方法はありますか?

ある種の「モジュールまたはアセンブリ初期化子」が必要なようです。そのようなことは IL には存在しないと思いますが (間違っている可能性もあります)、C# には絶対に存在しません。

いつでも何らかの属性を作成し、リフレクションを使用してその属性で装飾されたすべての型を見つけ、明示的に初期化することができます。(ジェネリック型ではややこしくなることに注意してください...おそらく、非ジェネリック型に制限したいと思うでしょう。)

編集:さらにいくつかのオプションが見つかりました:

  • Mono Cecil を使用して、任意の名前空間を検索するイニシャライザを作成するポストビルド ツールModuleInitializer.Run
  • Fodyのモジュール初期化アドオン

編集:より多くの文脈で、いわば病気よりも悪い治療法があるのではないかと思います. リフレクションベースの「この属性を持つすべてのパーサーを検索する」(または類似の) ものを作成したい開発者は、やるべきことはあまりありませんが、自分のアプリケーションの起動に干渉したくないと思います。

何も課すことなく他の人の生活を楽にするために、その反射部分をいつでも自分で含めることができます。

public static void RegisterAllParsers(Assembly assembly)

...おそらく属性ベースです。もちろん、静的解析メソッドを賢明に選択することしかできませんでした-開発者がファクトリの初期化に応じてさまざまな方法で解析できるファクトリを持っている場合、それを自動的に簡単に登録することはできません。

次に、開発者は次のように呼び出す必要があります。

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

始めるとき。これはおそらく、覚えておくのはそれほど難しくありません。また、ほとんどのアプリケーションには、単一のエントリ ポイントか、少なくともいくつかの共通の起動コードしかありません。

于 2013-03-22T18:56:51.693 に答える
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 に答える
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 に答える
1

この (名前空間)にはManaged Extensibility Framework (MEF)の使用を検討することを強くお勧めします。System.ComponentModel.Compositionクライアントは[Export(typeof(ISomeParserInterface))]属性を追加するだけで、MEF は利用可能なすべての拡張機能をパーサーに提供できます。

を使用しExportMetadataAttributeて、遭遇する要素に実際に必要なパーサーのみをコードでインスタンス化できるようにすることもできます。

[Export(typeof(ISomeParserInterface))]
[ExportMetadata("ElementName", "SomeXmlElement")]
于 2013-04-05T21:39:32.997 に答える