Понимание требований к инициализации поля С#
Учитывая следующий код:
public class Progressor
{
private IProgress<int> progress = new Progress<int>(OnProgress);
private void OnProgress(int value)
{
//whatever
}
}
Это дает следующую ошибку при компиляции:
Инициализатор поля не может ссылаться на нестатическое поле, метод или свойство "Progressor.OnProgress(int)"
Я понимаю ограничение, на которое он жалуется, но я не понимаю, почему это проблема, но вместо этого поле может быть инициализировано в конструкторе следующим образом:
public class Progressor
{
private IProgress<int> progress;
public Progressor()
{
progress = new Progress<int>(OnProgress);
}
private void OnProgress(int value)
{
//whatever
}
}
В чем разница в С# относительно инициализации поля и инициализации конструктора, которая требует этого ограничения?
Ответы
Ответ 1
Инициализация поля происходит до вызова конструктора базового класса, поэтому он не является допустимым объектом. Любой вызов метода с this
в качестве аргумента на данный момент приводит к непроверяемому коду и бросает VerificationException
, если недопустимый код не разрешен. Например: в прозрачном коде безопасности.
- 10.11.2 Инициализаторы переменных экземпляра
Когда конструктор экземпляра не имеет инициализатора конструктора или имеет инициализатор конструктора базы форм (...), этот конструктор неявно выполняет инициализации, указанные инициализаторами переменных полей экземпляра, объявленных в его классе. Это соответствует последовательности назначений, которые выполняются сразу после входа в конструктор и перед неявным вызовом конструктора прямого базового класса. Инициализаторы переменных выполняются в текстовом порядке, в котором они отображаются в объявлении класса. - 10.11.3 Выполнение конструктора
Переменные инициализаторы преобразуются в операторы присваивания, и эти операторы присваивания выполняются перед вызовом конструктора экземпляра базового класса. Это упорядочение гарантирует, что все поля экземпляров инициализируются их инициализаторами переменных до того, как будут выполнены любые операторы, имеющие доступ к этому экземпляру.
Ответ 2
Все в моем ответе - это только мои мысли о том, "почему было бы опасно допускать такой доступ". Я не знаю, была ли настоящая причина его ограничения.
С# spec говорит, что инициализация поля происходит в полях порядка, объявленных в классе:
10.5.5.2. Инициализация поля экземпляра
Инициализаторы переменных выполняются в текстовом порядке, в котором они появляются в объявлении класса.
Теперь скажем, что код, о котором вы упоминали, возможен - вы можете вызвать метод экземпляра из инициализации поля. Это сделает следующий код возможным:
public class Progressor
{
private string _first = "something";
private string _second = GetMyString();
private string GetMyString()
{
return "this is really important string";
}
}
Пока все хорошо. Но пусть немного злоупотребляет этой силой:
public class Progressor
{
private string _first = "something";
private string _second = GetMyString();
private string _third = "hey!";
private string GetMyString()
{
_third = "not hey!";
return "this is really important string";
}
}
Итак, _second
инициализируется до _third
. GetMyString
работает, _third
получить "не эй!" присваивается значение, но позже выполняется его инициализация собственного поля, и он устанавливается на `` hey! '. Не очень полезно и не удобочитаемо, правильно?
Вы также можете использовать _third
внутри метода GetMyString
:
public class Progressor
{
private string _first = "something";
private string _second = GetMyString();
private string _third = "hey!";
private string GetMyString()
{
return _third.Substring(0, 1);
}
}
Что вы ожидаете от значения _second
? Ну, перед началом инициализации поля все поля получают значения по умолчанию. Для string
это будет null
, поэтому вы получите неожиданный NullReferenceException
.
Итак, дизайнеры решили, что это просто не позволяет людям совершать такие ошибки вообще.
Вы могли бы сказать: "OK" запретить доступ к свойствам и вызовам, но разрешить использование полей, которые были объявлены выше того, с которым вы хотите получить доступ. Что-то вроде:
public class Progressor
{
private string _first = "something";
private string _second = _first.ToUpperInvariant();
}
но не
public class Progressor
{
private string _first = "something";
private string _second = _third.ToUpperInvariant();
private string _third = "another";
}
Это кажется полезным и безопасным. Но есть еще способ оскорбить его!
public class Progressor
{
private Lazy<string> _first = new Lazy<string>(GetMyString);
private string _second = _first.Value;
private string GetMyString()
{
// pick one from above examples
}
}
И все проблемы с методами снова возвращаются.
Ответ 3
Раздел 10.5.5.2: Инициализация поля экземпляра описывает это поведение:
Инициализатор переменных для поля экземпляра не может ссылаться на созданный экземпляр. Таким образом, это ошибка времени компиляции для ссылки this
в переменном инициализаторе, так как это ошибка времени компиляции для инициализатор переменной для ссылки на любой экземпляр через простое имя
Это поведение относится к вашему коду, потому что OnProgress
- это неявная ссылка на создаваемый экземпляр.
Ответ 4
Ответ более или менее, дизайнеры С# предпочли его таким образом.
Поскольку все инициализаторы полей переводятся в инструкции в конструкторе (конструкторах), которые идут перед любыми другими инструкциями в конструкторе, нет технических причин, почему это не должно быть возможным. Таким образом, это выбор дизайна.
Хорошая вещь о конструкторе заключается в том, что он дает понять, в каком порядке выполняются назначения.
Обратите внимание, что с членами static
разработчики С# выбрали по-разному. Например:
static int a = 10;
static int b = a;
разрешено и отличается от него (также разрешено):
static int b = a;
static int a = 10;
что может сбить с толку.
Если вы сделаете:
partial class C
{
static int b = a;
}
и в другом месте (в другом файле):
partial class C
{
static int a = 10;
}
Я даже не думаю, что будет четко определено, что произойдет.
Конечно, для вашего конкретного примера с делегатами в инициализаторе поля экземпляра:
Action<int> progress = OnProgress; // ILLEGAL (non-static method OnProgress)
действительно нет проблем, поскольку это не чтение или вызов нестатического элемента. Скорее используется информация о методе, и она не зависит от какой-либо инициализации. Но, согласно спецификации языка С#, это ошибка времени компиляции.