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

Инициализатор поля, обращающийся к `this`: недопустимый в С#, действительный в Java?

Во-первых, введение:

Этот код:

class C
{
    int i = 5;
    byte[] s = new byte[i];
}

не удается скомпилировать следующую ошибку:

Инициализатор поля не может ссылаться на нестатическое поле, метод или свойство `C.i '

Resharper говорит нечто подобное: не удается получить доступ к нестатическому полю i в статическом контексте.

Это соответствует тому, что С# spec говорит - что инициализатор поля не может получить доступ к экземпляру, создаваемому в данный момент (this) или, в добавлении, любое из полей экземпляра:

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

Однако это отлично работает в Java:

class C {
    int i = 5;
    byte s[] = new byte[i]; //no errors here
}

Еще со мной? Хорошо, вот вопрос. Err, вопросы.

В гипотетическом мире, где это было бы справедливо в С#, мне интересно: возможно ли даже ? Если это так, , какие будут плюсы и минусы, которые он добавит в таблицу? Кроме того, поскольку он действительно поддерживается Java, делать те же самые плюсы и минусы для для Java? Или существует фундаментальная разница в том, как инициализаторы типов работают на двух языках?

4b9b3361

Ответ 1

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

Что касается того, почему эта функция законна в Java, вам придется спросить разработчика Java.

Ответ 2

В С# инициализаторы полей являются просто удобной семантикой для разработчика. Компилятор перемещает все инициализаторы полей в тело конструктора ABOVE, где вызов выполняется в базовый конструктор. Итак, поля инициализируются в цепочке предков, и класс инициализируется из базы вниз.

Статические ссылки в порядке, потому что они инициализируются раньше всего.

Ответ 3

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

Там есть фундаментальное различие, и я думаю, что ответы на другие вопросы связаны с этой разницей.
Он находится в порядке инициализации типа, особенно в контексте наследования.

Итак, как работает инициализация экземпляра?

В С#:

  • все инициализаторы полей экземпляра запускаются сначала, "вверх" цепочка наследования, от большинства полученных до базового класса.

  • то ctors запускают "вниз" цепочку от базы к производной.

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

Что в принципе происходит, это выполняется для каждого chass в цепочке, начиная с самого полученного:

Derived.initialize(){
    derivedInstance.field1 = field1Initializer();
    [...]
    Base.Initialize();
    Derived.Ctor();
}

Простой пример показывает это:

void Main()
{
    new C();
}
class C: B {
    public int c = GetInt("C.c");
    public C(){
        WriteLine("C.ctor");
    }
}
class B {
    public int b = GetInt("B.b");
    public static int GetInt(string _var){
        WriteLine(_var);
        return 6;
    }
    public B(){
        WriteLine("B.ctor");
    }
    public static void WriteLine(string s){
        Console.WriteLine(s);
    }
}

Выход:

C.c
B.b
B.ctor
C.ctor

Это означает, что если доступ к полям в инициализаторе поля был действительным, я мог бы сделать следующее:

class C: B {
    int c = b; //b is a field inherited from the base class, and NOT YET INITIALIZED!
    [...]
}

В Java:

Длинная интересная статья о инициализации типа здесь. Подводя итог:

Это немного сложнее, потому что помимо понятия инициализаторов полей экземпляра существует понятие инициализатора (необязательного) экземпляра, но здесь его суть:

Все работает вниз цепочка наследования.

  • Инициализатор экземпляра класса base запускает
  • инициализаторы полей базового класса run
  • ctor (s) базового класса run

  • повторите шаги выше для следующего класса по цепочке наследования.

  • повторите предыдущий шаг до достижения самого производного класса.

Здесь доказательство: (или запустите его самостоятельно в Интернете)

class Main
{
    public static void main (String[] args) throws java.lang.Exception
    {
      new C();
    }
}

class C extends B {
    {
        WriteLine("init C");
    }
    int c = GetInt("C.c");

    public C(){
            WriteLine("C.ctor");
    }

}

class B {
    {
        WriteLine("init B");
    }
    int b = GetInt("B.b");

    public static int GetInt(String _var){
            WriteLine(_var);
            return 6;
    }
    public B(){
            WriteLine("B.ctor");
    }
    public static void WriteLine(String s){
            System.out.println(s);
    }

}

Вывод:

init B
B.b
B.ctor
init C
C.c
C.ctor

Это означает, что к моменту запуска инициализатора поля все унаследованные поля уже инициализированы (инициализатором OR ctor в базовом классе), поэтому он достаточно безопасен, чтобы разрешить такое поведение:

class C: B {
    int c = b; //b is inherited from the base class, and it already initialized!
    [...]
}

В Java, как и на С#, инициализаторы полей выполняются в порядке объявления.
Компилятор Java даже пытается проверить, что инициализаторы полей не вызываются вне порядка *:

class C {
    int a = b; //compiler error: illegal forward reference
    int b = 5;
}

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

class C {
    public int a = useB(); //after initializer completes, a == 0
    int b = 5;
    int useB(){
        return b;  //use b regardless if it was initialized or not.
    }
}

Ответ 4

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

class C 
{
    int i = 5;
    byte[] s;

    public C()
    {
        s = new byte[i];
    }
}

Ответ 5

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

В любом случае, вы можете просто сделать я const (как и должно быть) в любом случае.