web-dev-qa-db-ja.com

静的コンストラクタを実行するにはどうすればよいですか?

インスタンスを作成せずに、クラスの静的コンストラクタを実行したい(つまり、クラスを「ロードしたい」)。それ、どうやったら出来るの?

おまけの質問:.NET 4と以前のバージョンの間に違いはありますか?

編集:

  • クラスは静的ではありません。
  • 実行には時間がかかるため、インスタンスを作成する前に実行したいと思います。また、最初のアクセスでのこの遅延を回避したいと思います。
  • 静的なctorはprivate static readonlyフィールドを初期化するため、代わりにメソッドで実行することはできません。
42
mafu

他の答えはすばらしいですが、型への参照(つまりリフレクション)なしでクラスコンストラクターを強制的に実行する必要がある場合は、 RunClassConstructor を使用できます。

Type type = ...;
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
110
Gary Linscott

静的フィールドの1つを参照するだけです。これにより、静的初期化コードが強制的に実行されます。例えば:

public class MyClass
{
    private static readonly int someStaticField;

    static MyClass() => someStaticField = 1;

    // any no-op method call accepting your object will do fine
    public static void TouchMe() => GC.KeepAlive(someStaticField);
}

使用法:

// initialize statics
MyClass.TouchMe();
17
Fábio Batista

Cctor(静的コンストラクタ)は、次のいずれかが発生するたびに呼び出されます。

  1. クラスのインスタンスを作成します
  2. 静的メンバーにアクセスする
  3. BeforeFieldInit が設定されている場合、その前の任意の時点

他の静的メンバーがあると想定して、明示的にcctorを呼び出したい場合は、それらに呼び出し/アクセスするだけです。

Cctorで非常に興味深いことを何もしていない場合、コンパイラーはそれをBeforeFieldInitとマークすることを決定できます。これにより、CLRがcctorを早期に実行するオプションが許可されます。これについては、ここで詳しく説明します。 http://blogs.msdn.com/davidnotario/archive/2005/02/08/369593.aspx

6
csauve

Fábioの observations を拡張して、次の短くて完全なテストプログラムは TypeAttributes.BeforeFieldInit 動作のJITセンシティブな詳細を公開し、比較。NET 3.5最新バージョン(2017年後半)。NET 4.7.1、および各バージョン自体内のビルドタイプバリエーションの潜在的な危険性を示します。[1]

using System;
using System.Diagnostics;

class MyClass
{
    public static Object _field = Program.init();

    public static void TouchMe() { }
};

class Program
{
    static String methodcall, fieldinit;

    public static Object init() { return fieldinit = "fieldinit"; }

    static void Main(String[] args)
    {
        if (args.Length != 0)
        {
            methodcall = "TouchMe";
            MyClass.TouchMe();
        }
        Console.WriteLine("{0,18}  {1,7}  {2}", clrver(), methodcall, fieldinit);
    }
};

以下は、このプログラムを{x86、x64}{Debug、Release}のすべての組み合わせで実行した場合のコンソール出力です。 2つの.NETバージョンの違いを強調するために、手動でデルタシンボルΔ(プログラムでは出力されません)を追加しました。

。NET 2.0/3.5

2.0.50727.8825 x86 Debug
2.0.50727.8825 x86 Debug TouchMe fieldinit
2.0.50727.8825 x86 Release fieldinit
2.0.50727.8825 x86 Release TouchMe fieldinit
2.0.50727.8825 x64 Debug
2.0.50727.8825 x64 Debug TouchMe fieldinit
2.0.50727.8825 x64 Release
2.0.50727.8825 x64 Release TouchMe fieldinit

。NET 4.7.1

4.7.2556.0 x86 Debug
4.7.2556.0 x86 Debug TouchMe fieldinit
4.7.2556.0 x86 Release Δ
4.7.2556.0 x86 Release TouchMe Δ
4.7.2556.0 x64 Debug
4.7.2556.0 x64 Debug TouchMe fieldinit
4.7.2556.0 x64 Release
4.7.2556.0 x64 Release TouchMe Δ

イントロで述べたように、おそらくバージョンより興味深い2./.54.7デルタは違いwithin現在の.NETバージョン。これは、フィールド初期化の動作がx86x64の間で以前よりも一貫しているものの、まだ体験できることを示しているためです。今日のDebugビルドとReleaseビルドのランタイムフィールド初期化動作の大きな違い。

セマンティクスは、クラス上でばらばらな、または一見無関係な静的メソッドを呼び出すかどうかによって異なります。そのため、これが全体的な設計にバグをもたらす場合、かなり不思議で追跡が困難になる可能性があります。


メモ
1。上記のプログラムは、次のユーティリティ関数を使用して、現在の[〜#〜] clr [〜#〜]バージョンを表示します。

static String clrver()
{
    var s = typeof(Uri).Assembly.Location;
    return FileVersionInfo.GetVersionInfo(s).ProductVersion.PadRight(14) +
        (IntPtr.Size == 4 ? " x86 " : " x64 ") +
#if DEBUG
        "Debug  ";
#else
        "Release";
#endif
}
3
Glenn Slayden

また、これを行うことができます:

type.TypeInitializer.Invoke(null, null);
2
zumalifeguard

静的メソッドにアクセスするときに、静的コンストラクターが常に呼び出されるわけではありません!

基本クラスで静的メソッドを呼び出すと、スーパークラスの静的コンストラクターが呼び出されないことに気付きました。この予期しない動作は何度も噛み付きました。

1
ScottK

私はあなたのユースケースがこれのために何であるか正確にはわかりませんが、部分クラスと内部クラスを使用して、静的初期化子をかなりハッキーな方法で強制的に実行できます:

アイデアは、内部クラスの別の静的フィールドにアクセスする静的フィールドを持つ部分的な外部クラスを使用することです。外部クラスが静的に初期化されると、静的フィールドの初期化により、すべての内部クラスの静的初期化が開始されます。


public partial class OutterClass
{
    // When OutterClass is initialized, this will force InnerClass1 to be initialized.
    private static int _innerClass1Touched = InnerClass1.TouchMe;

    public static class InnerClass1
    {
        public static int TouchMe = 0;

        static InnerClass1()
        {
            Console.WriteLine("InnerClassInitialized");
        }
    }
}

public partial class OutterClass
{
    // When OutterClass is initialized, this will force InnerClass2 to be initialized.
    private static int _innerClass2Touched = InnerClass2.TouchMe;

    public static class InnerClass2
    {
        public static int TouchMe = 0;

        static InnerClass2()
        {
            Console.WriteLine("InnerClass2Initialized");
        }
    }
}

次に、アプリケーションの初期のどこかで、インスタンスの構築など、静的な初期化が行われるような方法でOutterClassを参照する必要があります。

より現実的な例は...

public interface IService
{
    void SayHello();
}

public partial class ServiceRegistry
{
    private static List<Func<IService>> _serviceFactories;

    private static void RegisterServiceFactory(Func<IService> serviceFactory)
    {
        // This has to be lazily initialized, because the order of static initialization
        //  isn't defined. RegisterServiceFactory could be called before _serviceFactories
        //  is initialized.
        if (_serviceFactories == null)
            _serviceFactories = new List<Func<IService>>();

        _serviceFactories.Add(serviceFactory);
    }

    public List<IService> Services { get; private set; }

    public ServiceRegistry()
    {
        Services = new List<IService>();

        foreach (var serviceFactory in _serviceFactories)
        {
            Services.Add(serviceFactory());
        }
    }
}


// In another file (ServiceOne.cs):
public class ServiceOne : IService
{
    void IService.SayHello()
    {
        Console.WriteLine("Hello from ServiceOne");
    }
}

public partial class ServiceRegistry
{
    // When OutterClass is initialized, this will force InnerClass1 to be initialized.
    private static int _serviceOneRegistryInitializer = ServiceOneRegistry.Initialize;

    private static class ServiceOneRegistry
    {
        public static int Initialize = 0;

        static ServiceOneRegistry()
        {
            ServiceRegistry.RegisterServiceFactory(() => new ServiceOne());
        }
    }
}

// In another file (ServiceTwo.cs):
public class ServiceTwo : IService
{
    void IService.SayHello()
    {
        Console.WriteLine("Hello from ServiceTwo");
    }
}

public partial class ServiceRegistry
{
    // When OutterClass is initialized, this will force InnerClass1 to be initialized.
    private static int _serviceTwoRegistryInitializer = ServiceTwoRegistry.Initialize;

    private static class ServiceTwoRegistry
    {
        public static int Initialize = 0;

        static ServiceTwoRegistry()
        {
            ServiceRegistry.RegisterServiceFactory(() => new ServiceTwo());
        }
    }
}

static void Main(string[] args)
{
    ServiceRegistry registry = new ServiceRegistry();
    foreach (var service in registry.Services)
    {
        serivce.SayHello();
    }

    // Output will be:
    // Hello from ServiceOne
    // Hello from ServiceTwo

    // No guarantee on order.
}

なぜこれを行うのですか?それは非常に狭いユースケースを持っています。これにより、すべてのサービスを初期化して登録する単一のメソッドが不要になります。私が個人的にその単一メソッドの初期化を排除したいのは、コード生成のためです。

0
Greg Hanes