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


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/…
— Воутер Шут

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 06

3
Он использует => вместо =, что делает его свойством.
— Винсент

3
Будьте осторожны при использовании этого метода, так как using =>не устанавливает фактическое значение, но будет выполнять код при каждом defaultReminderдоступе. Это может быть
— непреднамеренно

4

private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];является инициализатором поля и выполняется первым (до того, как для любого поля без инициализатора будет установлено значение по умолчанию, и до того, как будет выполнен вызываемый конструктор экземпляра). Поля экземпляра, у которых нет инициализатора, будут иметь допустимое (по умолчанию) значение только после завершения всех инициализаторов полей экземпляра. Из-за порядка инициализации конструкторы экземпляров выполняются последними, поэтому экземпляр еще не создается в момент выполнения инициализаторов. Следовательно, компилятор не может позволить ссылаться на какое-либо свойство экземпляра (или поле) до того, как экземпляр класса будет полностью построен. Это связано с тем, что любой доступ к переменной экземпляра, например, reminderнеявно ссылается на instance ( this), чтобы сообщить компилятору конкретную ячейку памяти для использования экземпляра.

Это также причина, по которой thisнельзя использовать инициализатор поля экземпляра.

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

Единственные члены типа, которые гарантированно инициализируются перед выполнением инициализаторов полей экземпляра, - это инициализаторы полей класса (статические), конструкторы классов (статические) и методы класса. Поскольку статические члены не зависят от экземпляра, на них можно ссылаться в любое время:

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 Docs: объявления классов .

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.