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

Понимание требований к инициализации поля С#

Учитывая следующий код:

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

В чем разница в С# относительно инициализации поля и инициализации конструктора, которая требует этого ограничения?

4b9b3361

Ответ 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)

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