Ініціалізатор поля, що має доступ до `this`: недійсний у C #, дійсний у Java?

По-перше, вступ:

Цей код:

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

не складається з наступною помилкою:

Ініціалізатор поля не може посилатися на нестатичне поле, метод або властивість `C.i '

Resharper says something similar: Cannot access non-static field i in static context.

Це вбудоване в те, що Специфікація C# показує - ініціалізатор поля не може отримати доступ до створеного в даний час екземпляра ( this ) або, за розширенням, будь-якого поля екземпляра:

Змінні ініціалізатор для поля екземпляра не можуть посилатися на   наприклад, створюється. Таким чином, це посилання на компіляцію часу   це в змінній ініціалізатора, оскільки це помилка часу компіляції для a   змінний ініціалізатор для посилання на будь-який член екземпляра через a   просте ім'я.

Однак, це добре працює в Java:

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

Все ще зі мною? Гаразд, ось питання. Err, питання.

У гіпотетичному світі, де це буде дійсним в C#, я задаюсь питанням: чи буде це навіть можливим ? Якщо це так, які б плюси і мінуси , які вони додадуть до таблиці? Крім того, оскільки вона насправді підтримується Java, роблять те ж саме плюси/мінуси для Java ? Або чи існує принципова різниця у тому, як ініціалізатори типу працюють на двох мовах?

12
@GuyDavid Що ви маєте на увазі "підставки розглядаються як статичні"?
додано Автор Cristi Diaconescu, джерело
@GuyDavid Повідомлення про помилку є ясним, вказуючи на те, що поле i є протилежним до статичного : не може посилатися на поле нестаціонарне [.. .] Ci
додано Автор Cristi Diaconescu, джерело
Дякуємо, що вказали, що я повинен був прочитати його більш ретельно (так що ви повинні ігнорувати мій другий коментар). Однак, якщо додати статичний до i , помилка повинна зникнути, оскільки з контексту методу вона може посилатися лише на властивості, які не належать до класу (клас, а не екземпляри).
додано Автор Novak, джерело
Припущення прийшло в той момент, коли я побачив помилку компілятора - він шукає статичне властивість C.i , а не i .
додано Автор Novak, джерело
ймовірно, за межами контексту будь-яких методів, властивості розглядаються як статичні, якщо ви не вказуєте його точне джерело? Моє пропозиція: перемістіть ваш ініціалізатор до вашого конструктора.
додано Автор Novak, джерело
Чи може хтось пояснити, чому специфікація обмежує незаконність у випадку, коли ви звертаєтеся до члена примірника через " просте ім'я "? Що це означає? І який, здавалося б, альтернативний механізм був би законним?
додано Автор InBetween, джерело

10 Відповіді

Коротше кажучи, можливість доступу до приймача перед тим, як працює конструктор, є функцією граничних переваг, що полегшує написання помилкових програм. Тому розробники мови C# повністю вимкнули його. Якщо вам потрібно використовувати приймач, поставте цю логіку в корпус конструктора.

а чому ця функція є законною в Java, вам доведеться попросити дизайнера Java.

13
додано
Цікаво, що Typecript просто увімкнув цю поведінку.
додано Автор MgSam, джерело
@ MgSam: Цікаво Я більше не живу з іншими розробниками TypeScript і не працюю, тому я не знайомий з останніми змінами. Я зазначаю, що, звичайно, TypeScript має дуже різні принципи дизайну, ніж C #.
додано Автор Eric Lippert, джерело

Коротше кажучи, можливість доступу до приймача перед тим, як працює конструктор, є функцією граничних переваг, що полегшує написання помилкових програм. Тому розробники мови C# повністю вимкнули його. Якщо вам потрібно використовувати приймач, поставте цю логіку в корпус конструктора.

а чому ця функція є законною в Java, вам доведеться попросити дизайнера Java.

13
додано
Цікаво, що Typecript просто увімкнув цю поведінку.
додано Автор MgSam, джерело
@ MgSam: Цікаво Я більше не живу з іншими розробниками TypeScript і не працюю, тому я не знайомий з останніми змінами. Я зазначаю, що, звичайно, TypeScript має дуже різні принципи дизайну, ніж C #.
додано Автор Eric Lippert, джерело

У C# поле ініціалізаторів - це просто семантика зручності для розробника. Компілятор переміщує всі ініціалізатори поля в тіло конструктора вище , де здійснюється виклик базового конструктора. Таким чином, поля ініціалізуються, піднімаючись до ланцюга предків, і клас ініціалізується з бази вниз.

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

5
додано
+1 для "серпня" :)
додано Автор Cristi Diaconescu, джерело
Цілком вірно. Але це означає, що було б заборонено використовувати поле типу base (яке ще не ініціалізовано) при оцінці ініціалізації. Проте раніше оголошені поля цього класу повинні бути в межах і ініціалізуватися. Тому я не бачу з'єднання між порядком виклику ініціалізатора та обмеженням, про яке я прошу.
додано Автор Cristi Diaconescu, джерело
@CristiDiaconescu, з приводу того, чому поле не в обсязі, ви можете прочитати відповідь, дану у серпні, і почитати пана Ліпперта тут -field "title =" не вдається отримати доступ до статичного поля "> stackoverflow.com/questions/1430787/…
додано Автор Gayot Fow, джерело

У C# поле ініціалізаторів - це просто семантика зручності для розробника. Компілятор переміщує всі ініціалізатори поля в тіло конструктора вище , де здійснюється виклик базового конструктора. Таким чином, поля ініціалізуються, піднімаючись до ланцюга предків, і клас ініціалізується з бази вниз.

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

5
додано
+1 для "серпня" :)
додано Автор Cristi Diaconescu, джерело
Цілком вірно. Але це означає, що було б заборонено використовувати поле типу base (яке ще не ініціалізовано) при оцінці ініціалізації. Проте раніше оголошені поля цього класу повинні бути в межах і ініціалізуватися. Тому я не бачу з'єднання між порядком виклику ініціалізатора та обмеженням, про яке я прошу.
додано Автор Cristi Diaconescu, джерело
@CristiDiaconescu, з приводу того, чому поле не в обсязі, ви можете прочитати відповідь, дану у серпні, і почитати пана Ліпперта тут -field "title =" не вдається отримати доступ до статичного поля "> stackoverflow.com/questions/1430787/…
додано Автор Gayot Fow, джерело

У жодному разі це не відповідь авторитетної , але дозвольте мені зробити вишукану здогадку.

Існує принципова різниця, і я думаю, що відповіді на інші питання пов'язані з цією різницею.
Вона полягає в ініціалізації порядку, особливо в контексті успадкування.

Отже, як працює ініціалізація примірника?

У C #:

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

  • тоді ктори запускаються, "вниз" ланцюжку, від бази до похідного.

Можливість виклику фахівців, котрі закликають когось іншого або (виразно) виклику фахівців базових класів, не змінює ситуацію, тому я залишу це.

Що відбувається в принципі, це відбувається для кожного шару в ланцюгу, починаючи з найбільш похідних:

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:

Довга цікава стаття про ініціалізацію типу тут . Узагальнити:

Це трохи складніше, тому що окрім поняття ініціалізаторів поля екземпляра існує поняття (необов'язковий) ініціалізатора прикладів , але ось його суть:

Усе діє вниз ланцюжка успадкування.

  • ініціалізатор екземпляра класу базовий запускається
  • ініціалізатори поля базового класу запускаються
  • запускаються ctor (s) базового класу

  • Повторіть наведені вище кроки для наступного класу вниз по спадщини.

  • повторити попередній крок до досягнення найбільш похідного класу.

Ось доказ: ( або запустіть його в Інтернеті )

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's already initialized!
    [...]
}

У Java, як і в C#, ініціалізатори поля виконуються у порядку декларації.
Компілятор Java навіть проходить перевірку того, що ініціалізатори поля не називаються поза замовленням *:

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

* Окрім того, ви можете отримати доступ до полів поза замовленням, якщо ініціалізатор викликає метод instance для цього:

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.
    }
}
2
додано

У жодному разі це не відповідь авторитетної , але дозвольте мені зробити вишукану здогадку.

Існує принципова різниця, і я думаю, що відповіді на інші питання пов'язані з цією різницею.
Вона полягає в ініціалізації порядку, особливо в контексті успадкування.

Отже, як працює ініціалізація примірника?

У C #:

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

  • тоді ктори запускаються, "вниз" ланцюжку, від бази до похідного.

Можливість виклику фахівців, котрі закликають когось іншого або (виразно) виклику фахівців базових класів, не змінює ситуацію, тому я залишу це.

Що відбувається в принципі, це відбувається для кожного шару в ланцюгу, починаючи з найбільш похідних:

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:

Довга цікава стаття про ініціалізацію типу тут . Узагальнити:

Це трохи складніше, тому що окрім поняття ініціалізаторів поля екземпляра існує поняття (необов'язковий) ініціалізатора прикладів , але ось його суть:

Усе діє вниз ланцюжка успадкування.

  • ініціалізатор екземпляра класу базовий запускається
  • ініціалізатори поля базового класу запускаються
  • запускаються ctor (s) базового класу

  • Повторіть наведені вище кроки для наступного класу вниз по спадщини.

  • повторити попередній крок до досягнення найбільш похідного класу.

Ось доказ: ( або запустіть його в Інтернеті )

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's already initialized!
    [...]
}

У Java, як і в C#, ініціалізатори поля виконуються у порядку декларації.
Компілятор Java навіть проходить перевірку того, що ініціалізатори поля не називаються поза замовленням *:

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

* Окрім того, ви можете отримати доступ до полів поза замовленням, якщо ініціалізатор викликає метод instance для цього:

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.
    }
}
2
додано

Це пов'язано з тим, що поле ініціалізатори переносяться в конструктор компілятором (якщо не статичний), і таким чином ви повинні бути явними у вашому конструкторі, як це:

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

    public C()
    {
        s = new byte[i];
    }
}
1
додано
Ваша відповідь надає орієнтовний шлях, очевидний. Він не відповідає на запитання чому .
додано Автор Cristi Diaconescu, джерело

Це пов'язано з тим, що поле ініціалізатори переносяться в конструктор компілятором (якщо не статичний), і таким чином ви повинні бути явними у вашому конструкторі, як це:

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

    public C()
    {
        s = new byte[i];
    }
}
1
додано
Ваша відповідь надає орієнтовний шлях, очевидний. Він не відповідає на запитання чому .
додано Автор Cristi Diaconescu, джерело

Це трохи невідповіді, але мені подобається думати що-небудь у тілі класу як незалежний від послідовності. Це не повинно бути послідовним кодом, який потрібно оцінити певним чином - це лише стан за замовчуванням для класу. Якщо ви використовуєте такий код, то ви очікуєте, що я оцінюватиму перед с.

У будь-якому випадку, ви можете просто створити const (як би це було), у будь-якому випадку.

1
додано
Насправді, ініціалізатори гарантовані будуть оцінені в порядку, у якому вони оголошені: msdn.microsoft.com/en-us/library/aa645757%28v=VS.71%29.aspx (шукайте фразу" текстовий порядок "). Тим не менш, я погоджуюся, що чистіше не писати код, використовуючи це (дещо незрозуміле) припущення.
додано Автор Cristi Diaconescu, джерело

Це трохи невідповіді, але мені подобається думати що-небудь у тілі класу як незалежний від послідовності. Це не повинно бути послідовним кодом, який потрібно оцінити певним чином - це лише стан за замовчуванням для класу. Якщо ви використовуєте такий код, то ви очікуєте, що я оцінюватиму перед с.

У будь-якому випадку, ви можете просто створити const (як би це було), у будь-якому випадку.

1
додано
Насправді, ініціалізатори гарантовані будуть оцінені в порядку, у якому вони оголошені: msdn.microsoft.com/en-us/library/aa645757%28v=VS.71%29.aspx (шукайте фразу" текстовий порядок "). Тим не менш, я погоджуюся, що чистіше не писати код, використовуючи це (дещо незрозуміле) припущення.
додано Автор Cristi Diaconescu, джерело
var chat = new Chat();
var chat = new Chat();
642 учасників

Обсуждение вопросов по C# / .NET / .NET Core / .NET Standard / Azure Сообщества-организаторы: — @itkpi — @dncuug

ІТ КПІ - Java
ІТ КПІ - Java
436 учасників