web-dev-qa-db-ja.com

基本クラスの静的コンストラクターが呼び出されるようにするための最良の方法は何ですか?

C#の静的コンストラクターに関するドキュメント は次のように述べています。

静的コンストラクターは、静的データを初期化するため、または1回だけ実行する必要がある特定のアクションを実行するために使用されます。最初のインスタンスが作成される前、または静的メンバーが参照される前に自動的に呼び出されます

その最後の部分(自動的に呼び出されるときについて)は、私をループに投げ込みました。そのパートIthoughtを読むまでは、何らかの方法でクラスにアクセスするだけで、その基本クラスの静的コンストラクターであると確信できました。呼ばれていた。ドキュメントをテストおよび調査したところ、これは当てはまらないことが明らかになりました。 baseクラスの静的コンストラクターは、その基本クラスのメンバーが特ににアクセスされるまで実行されることが保証されていないようです。

さて、派生クラスを扱っている場合、ほとんどの場合、インスタンスを構築し、これが作成中の基本クラスのインスタンスを構成するため、静的コンストラクターが呼び出されると思います。しかし、派生クラスのstaticメンバーのみを扱っている場合はどうなりますか?

これをもう少し具体的にするために、私は以下のコードが機能すると思いました

_abstract class TypeBase
{
    static TypeBase()
    {
        Type<int>.Name = "int";
        Type<long>.Name = "long";
        Type<double>.Name = "double";
    }
}

class Type<T> : TypeBase
{
    public static string Name { get; internal set; }
}

class Program
{
    Console.WriteLine(Type<int>.Name);
}
_

_Type<T>_クラスにアクセスすると、TypeBaseの静的コンストラクターが自動的に呼び出されると思いました。しかし、そうではないようです。 _Type<int>.Name_はnullであり、上記のコードは空の文字列を出力します。

ダミーメンバー(何もしない静的Initialize()メソッドなど)を作成する以外に、基本型の静的コンストラクターが前に呼び出されるようにするためのより良い方法がありますその派生型のどれが使用されますか?

そうでなければ...ダミーメンバーです!

35
Dan Tao

ここでのルール 非常に複雑です そしてCLR2.0とCLR4.0の間では実際に 微妙で興味深い方法で変更されました 、IMOはCLRバージョン間でほとんどの「賢い」アプローチを脆弱にします。 Initialize()メソッドalsoは、フィールドに触れない場合、CLR4.0では機能しない可能性があります。

別のデザインを探すか、タイプでregularレイジー初期化を使用します(つまり、ビットまたは参照(nullに対して)をチェックして、完了しました)。

13
Marc Gravell

静的コンストラクターを明示的に呼び出すことができるため、初期化のためのメソッドを作成する必要はありません。

System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (TypeBase).TypeHandle);

派生クラスの静的コンストラクターで呼び出すことができます。

25

他の人が指摘しているように、あなたの分析は正しいです。仕様はここで文字通り実装されています。基本クラスのメンバーが呼び出されておらず、インスタンスも作成されていないため、基本クラスの静的コンストラクターは呼び出されません。それがいかに驚くべきことかはわかりますが、それは仕様の厳密で正しい実装です。

「それをするときに痛いなら、それをしないでください」以外にあなたにアドバイスはありません。反対の場合もあなたを噛む可能性があることを指摘したかっただけです:

class Program 
{
  static void Main(string[] args)
  {      
    D.M();
  }      

}
class B 
{ 
  static B() { Console.WriteLine("B"); }
  public static void M() {}
} 
class D: B 
{ 
  static D() { Console.WriteLine("D"); }
}

これは、「Dのメンバー」が呼び出されたにもかかわらず、「B」を出力します。 Mは継承のみによってDのメンバーです。 CLRには、B.Mが「Dを介して」呼び出されたのか「Bを介して」呼び出されたのかを区別する方法がありません。

17
Eric Lippert

すべてのテストで、ベースのダミーメンバーを呼び出して、ベースが静的コンストラクターを呼び出すようにすることしかできませんでした。

_class Base
{
    static Base()
    {
        Console.WriteLine("Base static constructor called.");
    }

    internal static void Initialize() { }
}

class Derived : Base
{
    static Derived()
    {
        Initialize(); //Removing this will cause the Base static constructor not to be executed.
        Console.WriteLine("Derived static constructor called.");
    }

    public static void DoStaticStuff()
    {
        Console.WriteLine("Doing static stuff.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Derived.DoStaticStuff();
    }
}
_

もう1つのオプションは、次のことを行う派生型に静的な読み取り専用メンバーを含めることでした。

private static readonly Base myBase = new Base();

ただし、これは、基本の静的コンストラクターを呼び出すためだけのハックのように感じます(ダミーメンバーもそうですが)。

3
Joshua Rodgers

私はいつもこのようなものに頼っていることを後悔しています。静的メソッドとクラスは、後で制限する可能性があります。後でTypeクラスの特別な動作をコーディングしたい場合は、ボックスに入れられます。

だからここにあなたのアプローチのわずかなバリエーションがあります。これはもう少しコードですが、後でカスタムタイプを定義して、カスタム処理を実行できるようになります。

    abstract class TypeBase
    {
        private static bool _initialized;

        protected static void Initialize()
        {
            if (!_initialized)
            {
                Type<int>.Instance = new Type<int> {Name = "int"};
                Type<long>.Instance = new Type<long> {Name = "long"};
                Type<double>.Instance = new Type<double> {Name = "double"};
                _initialized = true;
            }
        }
    }

    class Type<T> : TypeBase
    {
        private static Type<T> _instance;

        public static Type<T> Instance
        {
            get
            {
                Initialize();
                return _instance;
            }
            internal set { _instance = value; }
        }

        public string Name { get; internal set; }
    }

その後、仮想メソッドをTypeに追加し、Typeの特別な実装が必要になったときに、次のように実装できます。

class TypeInt : Type<int>
{
    public override string Foo()
    {
        return "Int Fooooo";
    }
}

そして、変更して接続します

protected static void Initialize()
{
      if (!_initialized)
      {
          Type<int>.Instance = new TypeInt {Name = "int"};
          Type<long>.Instance = new Type<long> {Name = "long"};
          Type<double>.Instance = new Type<double> {Name = "double"};
          _initialized = true;
       }
}

私のアドバイスは、静的コンストラクターを避けることです-それは簡単です。また、静的クラスと、可能な場合は静的メンバーを避けてください。私は決して控えめに言っているのではありません。静的よりもクラスのシングルトンを優先します。

3
Neil

ただのアイデアですが、次のようなことができます。

    abstract class TypeBase
    {
        static TypeBase()
        {
            Type<int>.Name = "int";
            Type<long>.Name = "long";
            Type<double>.Name = "double";
        }
    }

    class Type<T> : TypeBase
    {
        static Type() 
        {
            new Type<object>();
        }

        public static string Name { get; internal set; }
    }

    class Program
    {
        Console.WriteLine(Type<int>.Name);
    }
0
HABJAN