Подтвердить что ты не робот

Как называется лучший статический конструктор базового класса?

Документация

Я предположил, что доступ к классу Type<T> автоматически вызовет статический конструктор для TypeBase; но это, похоже, не так. Type<int>.Name - null, а приведенный выше код выводит пустую строку.

Помимо создания некоторого фиктивного элемента (например, статического метода Initialize(), который ничего не делает), существует лучший способ гарантировать, что статический конструктор базового типа будет вызываться до использования любого из его производных типов?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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". M является членом D исключительно по наследству; CLR не имеет возможности отличить, было ли вызвано B.M "через D" или "через B".

Ответ 4

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

Итак, вот небольшая вариация вашего подхода. Это немного больше кода, но он позволит вам иметь настраиваемый тип, определенный позже, который позволяет выполнять пользовательские вещи.

    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, вы можете реализовать это:

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

Мой совет: избегать статических конструкторов - это легко сделать. Также избегайте статических классов и, где возможно, статических элементов. Я не говорю никогда, просто экономно. Предпочитайте одноэлемент класса статическому.

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

Вы можете попробовать следующее:

public class Program
{
    public static void Main(string[] args)
    {
        Instance.UpdateIndexForItem(new Item { Id = 1 });
    }
}

public class Instance : BaseAbstract<Instance, Item>
{
    static Instance()
    {
        Console.WriteLine(nameof(Instance));
        UpdateItem = new Action<Item>(UpdateIndexForItem);
    }

    public static void UpdateIndexForItem(Item item)
    {
        Console.WriteLine("Instance UpdateIndexForItem Method Call!");
    }
}

public abstract class BaseAbstract<TInstance, TItem> where TItem : Item
{
    protected static Action<TItem> UpdateItem { get; set; }

    static BaseAbstract()
    {
        Activator.CreateInstance(typeof(TInstance));
    }

    public static void AddItemsToIndex(IEnumerable<TItem> items)
    {
        foreach (var item in items)
        {
            UpdateItem(item);
        }
    }
}

public class Item
{
    public long Id { get; set; }
}