Каков наилучший способ обеспечения вызова статического конструктора базового класса? - PullRequest
35 голосов
/ 11 января 2011

Документация о статических конструкторах в C # гласит:

Статический конструктор используется для инициализации любых статических данных или для выполнения определенного действия, которое необходимо выполнить только один раз.Он вызывается автоматически перед созданием первого экземпляра или ссылками на статические элементы .

Эта последняя часть (о том, когда он вызывается автоматически) бросила меня в цикл;до прочтения этой части я думал , что простым доступом к классу любым способом я мог быть уверен, что был вызван статический конструктор его базового класса.Тестирование и проверка документации показали, что это не так;похоже, что статический конструктор для класса 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() метода, который ничего не делает), есть лучший способчтобы гарантировать, что статический конструктор базового типа будет вызываться до того, как будет использован какой-либо из его производных типов?

Если нет, то ... это фиктивный член!

Ответы [ 6 ]

23 голосов
/ 11 января 2011

Вы можете вызвать статический конструктор explicity, поэтому вам не придется создавать какие-либо методы для инициализации:

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

Вы можете вызывать его в статическом конструкторе производного класса.

17 голосов
/ 11 января 2011

Как уже отмечали другие, ваш анализ верен. Спецификация реализована здесь буквально; поскольку ни один член базового класса не был вызван, а экземпляр не был создан, статический конструктор базового класса не вызывается. Я могу видеть, как это может быть удивительно, но это строгая и правильная реализация спецификации.

У меня нет для тебя никакого совета, кроме как "если тебе больно, когда ты это делаешь, не делай этого". Я просто хотел отметить, что противоположный случай также может укусить вас:

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

Это печатает "B", несмотря на то, что "член D" был вызван. М является членом D исключительно по наследству; CLR не может различить, вызван ли B.M «через D» или «через B».

13 голосов
/ 11 января 2011

Правила здесь очень сложны , и между CLR 2.0 и CLR 4.0 они фактически изменились тонким и интересным образом , что делает IMO наиболее "умными" подходами хрупкими между версиями CLR , Initialize() метод также может не выполнять работу в CLR 4.0, если он не касается полей.

Я бы искал альтернативный дизайн или, возможно, использовал бы обычную ленивую инициализацию в вашем типе (т. Е. Проверьте бит или ссылку (против null), чтобы убедиться, что это было сделано).

3 голосов
/ 11 января 2011

Я почти всегда сожалею, что полагался на что-то подобное.Статические методы и классы могут ограничить вас позже.Если вы захотите написать какое-то специальное поведение для вашего класса 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 голосов
/ 11 января 2011

Во всем моем тестировании я смог получить только вызов фиктивного члена на базе, чтобы база вызывала свой статический конструктор, как показано на рисунке:

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

Другой вариант включал статический член только для чтения в производный типизированный текст, который выполнял следующие действия:

private static readonly Base myBase = new Base();

Однако это похоже на хак (хотя и на фиктивный член) только для вызова базового статического конструктора.

0 голосов
/ 11 января 2011

Просто идея, вы можете сделать что-то вроде этого:

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