Нужны некоторые разъяснения о статическом конструкторе VS истории инициализатора статического поля в С#

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

После прочтения множества вопросов stackoverflow на эту тему и знаменитой статьи Джона Скита о флаге beforefieldinit я теперь мы гораздо лучше понимаем разницу между двумя стратегиями инициализации.

Есть один момент, в котором я не уверен, в основном потому, что я не смог найти никакой официальной документации по этому поводу.

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

Верно ли это даже для встроенной инициализации статических полей? Гарантируется ли однократное выполнение встроенной инициализации статического поля даже в многопоточных сценариях?

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

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

Вот пример:

public class SomeInterestingService 
{
   private static readonly int ConstantNumber = 13;
   private static readonly string[] Names = new[] { "bob", "alice" };

   private readonly INumberGenerator numberGenerator;

   public SomeInterestingService(INumberGenerator numberGenerator)
   {
      this.numberGenerator = numberGenerator ?? throw new ArgumenNullException(nameof(numberGenerator));
   }

   public int ComputeMagicNumber() 
   {
      int answer = this.numberGenerator.GetNumber();

       foreach(var name in names)
       {
          answer += name.Length;
       }

       answer += ConstantNumber;
       return answer;
   }
}

Есть ли в таком коде какая-либо практическая разница в выборе инициализации статического конструктора или встроенной инициализации статических полей ConstantNumber и Names, помимо разницы в производительности (встроенная инициализация более эффективна из-за оптимизации во время выполнения, которая невозможна при использовании статического конструктор) ?

Может ли правильность приведенного выше кода быть затронута coiche в каком-либо странном угловом случае? (Думаю, нет)


person Enrico Massone    schedule 15.11.2019    source источник
comment
Модификатор readonly делает вопрос довольно академическим — ConstantNumber всегда будет 13, даже если инициализация выполнялась дважды. Но теоретически Алису и Боба можно заменить.   -  person Henk Holterman    schedule 16.11.2019
comment
@HelkHolterman Я использовал константу просто в качестве примера, это было первое, что я придумал для примера кода. Я не понял вашу мысль о замене Алисы и Боба, не могли бы вы объяснить лучше? Спасибо   -  person Enrico Massone    schedule 16.11.2019


Ответы (1)


Оригинальный вопрос:

Есть ли в таком коде какая-либо практическая разница в выборе инициализации статического конструктора или встроенной инициализации статических полей ConstantNumber и Names, помимо разницы в производительности (встроенная инициализация более эффективна из-за оптимизации во время выполнения, которая невозможна при использовании статического конструктор) ?

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

Что говорит @Henk Holterman, поскольку массив имен является ссылочным типом, вы теоретически можете изменить любое значение в массиве. Нравиться:

Names[0] = "Henk Holterman";

Несмотря на то, что свойство доступно только для чтения. Это означает, что вы не можете назначить новый экземпляр массива этому свойству. Значения в массиве доступны не только для чтения. И им можно манипулировать, если он общедоступен или вызывает метод этого класса.

person Thomas Heijtink    schedule 15.11.2019