Почему я не могу инициализировать переменные только для чтения в инициализаторе? - PullRequest
17 голосов
/ 16 декабря 2010

Почему я не могу инициализировать переменные только для чтения в инициализаторе?Следующее не работает должным образом:

class Foo
{
    public readonly int bar;
}

new Foo { bar=0; }; // does not work

Это связано с некоторыми техническими ограничениями CLR?

РЕДАКТИРОВАТЬ

Iзнать, что new Foo { bar=0; } - это то же самое, что и new Foo().bar=0;, но CLR использует «только для чтения», или это просто ограничение компилятора?

Ответы [ 10 ]

17 голосов
/ 16 декабря 2010

Инициализатор - это просто синтаксический сахар.Когда вы пишете:

new Foo { bar=0; };

(что, кстати, является синтаксической ошибкой и должно быть таким ...)

new Foo { bar=0 }

, что на самом деле происходит:

var x = new Foo();
x.bar = 0;

Поскольку свойство доступно только для чтения, это второе утверждение недопустимо.

Редактировать: Исходя из ваших правок, вопрос немного неясен.Свойство readonly по конструкции не устанавливается.Он построен на объекте строительства.Это обеспечивается как компилятором, так и средой выполнения.(Следует признать, что я не проверял последнее, поскольку для обхода первого потребовалось бы немного хитрости.)

Имейте в виду, что существует два этапа «компиляции».Он применяется при компиляции кода C # в коде IL и при компиляции кода IL в машинный код.

Это не технический предел CLR, и он работает точно какследует, учитывая явное объявление readonly.После того, как объект построен, вы не можете установить свойство readonly.

14 голосов
/ 16 декабря 2010

Разрешение readonly для установки в инициализаторе вводит противоречия и сложности, которые не могут быть применены во время компиляции .Я предполагаю, что ограничение состоит в том, чтобы избежать двусмысленности.Большой ключ - проверка во время компиляции .

Представьте себе:

class Foo
{
    public readonly int bar;
    Foo () {
      // compiler can ensure that bar is set in an invoked ctor
      bar = 0;
    }
}

// compiler COULD know that `bar` was set in ctor
// and therefore this is invalid
new Foo { bar = 0; }

Теперь рассмотрим:

class Foo
{
    public readonly int bar;
    Foo () {
      // imagine case where bar not set in ctor
    }
}

// compiler COULD know that `bar` is not bound yet
// therefore, this COULD be valid
new Foo { bar = 0; }

// but this COULD be proved to never be valid
new Foo();

Представьте, что обавышеприведенные случаи унифицированы (скажем, «магией компилятора»), однако, введите в дженерики:

T G<T> () where T : new
{
  // What in heck should happen *at compile time*?
  // (Consider both cases above.)
  // What happens if T (Foo) changes to include/not-include setting the
  // readonly variable in the ctor?
  // Consider intermediate code that invokes G<Foo>() and this other
  // code is NOT recompiled even though Foo is--
  //   Yet a binary incompatibility has been added!
  //   No thanks!
  return new T();
}
G<Foo>();

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

5 голосов
/ 16 декабря 2010

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

3 голосов
/ 16 декабря 2010

Поскольку инициализатор эквивалентен

var foo = new Foo();
foo.bar=0;

Это переписать за кулисами.

2 голосов
/ 17 декабря 2010

То, что вы пытаетесь сделать, это:

   class Foo
   {
        public readonly int bar;

        Foo(int b)
        {
             bar = b;  // readonly assignments only in constructor
        }
   }

   Foo x = new Foo(0);
1 голос
/ 16 декабря 2010

Поскольку вы указали это только для чтения. Нет смысла указывать, что что-то доступно только для чтения, а затем ожидать, что оператор write будет работать.

0 голосов
/ 27 января 2019

Я знаю, что это не прямой ответ на вопрос автора, но более новая версия C # теперь позволяет прямую инициализацию из самого свойства следующим образом.

public int bar { get; set; } = 0;

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

0 голосов
/ 29 августа 2016

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

Если вы не утверждаете, что спецификации верны по определению, я считаю, что языковая спецификация здесь неверна. Это частично нарушает важную особенность языка, которая является понятием только для чтения. Проблемы неоднозначности, упомянутые в других ответах, имеют, на мой взгляд, одну причину. Readonly - это очень навязчивая функция, и трудно сделать правильный шаг на полпути относительно правильности констант, и, что более важно, вредно для разработанных стилей кодирования.

То, что вы ищете и, вероятно, тем временем нашли, называется именованными аргументами: https://stackoverflow.com/a/21273185/2712726 Это не то, о чем вы просили, но приближается.

Также, чтобы быть справедливым, я должен добавить, что есть очень хорошо осведомленные авторы, которые будут полностью не согласны с этими взглядами на правильность констант, которые часто имеют разработчики C ++. Эрик Липперт, который по общему признанию имеет блестящие должности, написал это (ужасающее для разработчиков C ++) утверждение: https://stackoverflow.com/a/3266579/2712726

0 голосов
/ 16 декабря 2010

Это результат реализации ключевого слова readonly . Приведенная ниже цитата взята из ссылки MSDN для только для чтения :

Ключевое слово readonly - это модификатор, который вы можете использовать в полях. Когда объявление поля включает модификатор readonly, присваивания полям, введенным объявлением, могут происходить только как часть объявления или в конструкторе того же класса.

0 голосов
/ 16 декабря 2010

Согласно этой странице , CLR по умолчанию создает объект сначала перед обработкой списка инициализаторов, и поэтому вы назначаете bar дважды (один раз при построении по умолчанию, один раз при обработке инициализатора).

...