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


96

У мене є клас, і коли я намагаюся використовувати його в іншому класі, я отримую повідомлення про помилку нижче.

using System;
using System.Collections.Generic;
using System.Linq;

namespace MySite
{
    public class Reminders
    {
        public Dictionary<TimeSpan, string> TimeSpanText { get; set; }

        // We are setting the default values using the Costructor
        public Reminders()
        {
            TimeSpanText.Add(TimeSpan.Zero, "None");
            TimeSpanText.Add(new TimeSpan(0, 0, 5, 0), "5 minutes before");
            TimeSpanText.Add(new TimeSpan(0, 0, 15, 0), "15 minutes before");
            TimeSpanText.Add(new TimeSpan(0, 0, 30, 0), "30 minutes before");
            TimeSpanText.Add(new TimeSpan(0, 1, 0, 0), "1 hour before");
            TimeSpanText.Add(new TimeSpan(0, 2, 0, 0), "2 hours before");
            TimeSpanText.Add(new TimeSpan(1, 0, 0, 0), "1 day before");
            TimeSpanText.Add(new TimeSpan(2, 0, 0, 0), "2 day before");
        }

    }
}

Використання класу в іншому класі

class SomeOtherClass
{  
    private Reminders reminder = new Reminders();
    // error happens on this line:
    private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 
    ....

Помилка (CS0236):

A field initializer cannot reference the nonstatic field, method, or property

Чому це відбувається і як це виправити?

Відповіді:


147

Цей рядок:

private dynamic defaultReminder = 
                          reminder.TimeSpanText[TimeSpan.FromMinutes(15)];

Ви не можете використовувати змінну екземпляра для ініціалізації іншої змінної екземпляра. Чому? Оскільки компілятор може їх переставити - немає жодної гарантії, що reminderвона буде ініціалізована раніше defaultReminder, тому у наведеному вище рядку може бути символ NullReferenceException.

Натомість просто використовуйте:

private dynamic defaultReminder = TimeSpan.FromMinutes(15);

Або встановіть значення в конструкторі:

private dynamic defaultReminder;

public Reminders()
{
    defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 
}

Детальніше про цю помилку компілятора на MSDN - Помилка компілятора CS0236 .


3
Java більше "прощає" для таких типів конструкцій. Не знаю, чи це добре. stackoverflow.com/questions/1494735/…
Wouter Schut

32
Ні, компілятор не може переставити ініціалізатори. Специфікація мови C # у розділі "10.5.5.2 Ініціалізація поля екземпляра" стверджує наступне: Ініціалізатори змінних виконуються в текстовому порядку, в якому вони з’являються в декларації класу. Це навіть повторюється в "10.11.2 Ініціатори змінних екземпляра", де вони кажуть: Ініціалізатори змінних виконуються в текстовому порядку, в якому вони з'являються в оголошенні класу. Отже, ваше пояснення помилкове. Порядок фіксований. Причина, через яку це заборонено, полягає в тому, що дизайнери C # хотіли, щоб це було саме так.
Jeppe Stig Nielsen

(Тільки у випадку partial classз "частинами" у декількох файлах порядок ініціалізаторів полів незрозумілий, але це стосується і staticполів!)
Джеппе Стіг Нільсен,

@WouterSchut Потік, який ви посилаєте, не стосується Java ?! Мова також йде про C #, однак із staticполями замість полів екземпляра.
Jeppe Stig Nielsen

2
@Andrew Зовсім неправда, багато рішень приймаються, щоб заборонити погані практики. навіть незважаючи на те, що теоретично вони можуть бути реалізовані, деякі охороняються Попередженнями, а деякі є простими помилками. і я думаю, що це один із таких випадків ... навіть якщо стандарт каже, що він послідовний, навіть досвідчений розробник не сказав би цього з упевненістю (без пошуку стандарту).
Tomer W

22

Вам потрібно помістити цей код у конструктор вашого класу:

private Reminders reminder = new Reminders();
private dynamic defaultReminder;

public YourClass()
{
    defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];
}

Причина полягає в тому, що ви не можете використовувати одну змінну екземпляра для ініціалізації іншої за допомогою польового ініціалізатора.


10

ви можете використовувати так

private dynamic defaultReminder => reminder.TimeSpanText[TimeSpan.FromMinutes(15)]; 

11
Ласкаво просимо до Stack Overflow! Незважаючи на те, що цей фрагмент коду може вирішити питання, включення пояснення дійсно допомагає поліпшити якість вашої публікації. Пам’ятайте, що ви будете відповідати на запитання для читачів у майбутньому, і ці люди можуть не знати причин вашої пропозиції коду. Будь ласка, також намагайтеся не переповнювати свій код пояснювальними коментарями, оскільки це зменшує читабельність як коду, так і пояснень!
jmattheis

3
Він використовує => замість = таким чином робить це властивістю.
Вінсент

3
Будьте обережні, використовуючи цей прийом, оскільки використання =>не встановлює фактичного значення, але виконуватиме код кожного разу, коли ви defaultReminderотримуєте доступ. Це може бути не задуманим, і мати негативний вплив на продуктивність, або створити небажаний тиск для ГХ тощо
Smilediver

4

private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];є ініціалізатором поля і виконується першим (перед тим, як для будь-якого поля без ініціалізатора встановлено значення за замовчуванням і до того, як буде запущений конструктор екземпляра, що викликається). Поля екземпляра, які не мають ініціалізатора, матимуть законне значення (за замовчуванням) лише після того, як будуть виконані всі ініціалізатори полів екземпляра. Через порядок ініціалізації конструктори екземплярів виконуються в останню чергу, саме тому екземпляр ще не створюється на момент виконання ініціалізаторів. Тому компілятор не може дозволити посилання на властивості екземпляра (або поля) до того, як екземпляр класу буде повністю побудований. Це пов’язано з тим, що будь-який доступ до змінної екземпляра, як reminderнеявно, посилається на instance ( this), щоб повідомити компілятору конкретне розташування пам’яті екземпляра для використання.

Це також причина, чому thisзаборонено в ініціалізаторі поля екземпляра.

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

Єдині члени типу, які гарантовано ініціалізуються до виконання полів екземпляра, є ініціалізатори полів класу (static) та конструктори класу (статичні) та методи класу. Оскільки статичні члени незалежні від екземпляра, на них можна посилатися в будь-який час:

class SomeOtherClass
{
  private static Reminders reminder = new Reminders();

  // This operation is allowed,
  // since the compiler can guarantee that the referenced class member is already initialized
  // when this instance field initializer executes
  private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];
}

Ось чому ініціалізаторам полів екземпляра дозволяється посилатися лише на член класу (статичний член). Ці правила ініціалізації компілятора забезпечать детермінований примірник типу.

Для більш детальної інформації я рекомендую цей документ: Документи Microsoft: Декларації класів .

Це означає, що поле екземпляра, яке посилається на іншого члена екземпляра для ініціалізації його значення, повинно бути ініціалізоване з конструктора екземпляра або оголошено посилальний член static.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.