Inicjator pola nie może odwoływać się do niestatycznego pola, metody ani właściwości


96

Mam klasę i kiedy próbuję użyć jej w innej klasie, pojawia się poniższy błąd.

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");
        }

    }
}

Korzystanie z klasy w innej klasie

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

Błąd (CS0236):

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

Dlaczego tak się dzieje i jak to naprawić?

Odpowiedzi:


147

Ta linia:

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

Nie można użyć zmiennej instancji do zainicjowania innej zmiennej instancji. Czemu? Ponieważ kompilator może zmienić ich kolejność - nie ma gwarancji, że reminderzostaną zainicjowane wcześniej defaultReminder, więc powyższa linia może spowodować wyświetlenie pliku NullReferenceException.

Zamiast tego użyj:

private dynamic defaultReminder = TimeSpan.FromMinutes(15);

Alternatywnie ustaw wartość w konstruktorze:

private dynamic defaultReminder;

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

Więcej szczegółowych informacji na temat tego błędu kompilatora można znaleźć w witrynie MSDN - błąd kompilatora CS0236 .


3
Java jest bardziej „wyrozumiała” dla tego rodzaju konstrukcji. Nie wiem, czy to dobrze. stackoverflow.com/questions/1494735/…
Wouter Schut

32
Nie, kompilator nie może zmienić kolejności inicjatorów. Specyfikacja języka C # podaje, w sekcji „10.5.5.2 Inicjalizacja pola instancji”, co następuje: Inicjatory zmiennych są wykonywane w kolejności tekstowej, w jakiej pojawiają się w deklaracji klasy. Jest to nawet powtórzone w "10.11.2 Inicjatory zmiennych instancji", gdzie powiedziano: Inicjatory zmiennych są wykonywane w kolejności tekstowej, w jakiej pojawiają się w deklaracji klasy. Więc twoje wyjaśnienie jest błędne. Zamówienie jest naprawione. Powodem, dla którego jest to niedozwolone, jest to, że projektanci C # chcieli tego w ten sposób.
Jeppe Stig Nielsen

(Tylko w przypadku a partial classz "częściami" w kilku plikach kolejność inicjatorów pola jest niejasna, ale dotyczy to również staticpól!)
Jeppe Stig Nielsen

@WouterSchut Wątek, który łączysz, nie dotyczy Javy ?! Chodzi również o C #, ale z staticpolami zamiast pól instancji.
Jeppe Stig Nielsen

2
@Andrew Wcale nieprawda, podjęto wiele decyzji, aby zakazać złych praktyk. chociaż teoretycznie można je zaimplementować, niektóre są chronione przez ostrzeżenia, a niektóre są zwykłymi błędami. i myślę, że jest to jeden z takich przypadków ... chociaż standard mówi, że jest sekwencyjny, nawet doświadczony programista nie powiedziałby tego z pewnością (bez przeszukiwania standardu).
Tomer W

22

Musisz umieścić ten kod w konstruktorze swojej klasy:

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

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

Powodem jest to, że nie można użyć jednej zmiennej instancji do zainicjowania innej za pomocą inicjatora pola.


10

możesz używać w ten sposób

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

11
Witamy w Stack Overflow! Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod. Prosimy również starać się nie wypełniać kodu komentarzami wyjaśniającymi, ponieważ zmniejsza to czytelność zarówno kodu, jak i wyjaśnień!
jmattheis

3
Używa => zamiast =, czyniąc go właściwością.
Vincent,

3
Zachowaj ostrożność, używając tej techniki, ponieważ użycie =>nie ustawia rzeczywistej wartości, ale wykonuje kod za każdym razem, gdy defaultReminderjest używany . Może to nie być zamierzone i mieć negatywny wpływ na wydajność lub generować niepożądany nacisk na GC itp.
Smilediver

4

private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];jest inicjatorem pola i jest wykonywana jako pierwsza (przed ustawieniem wartości domyślnej dla dowolnego pola bez inicjatora i przed wykonaniem wywoływanego konstruktora wystąpienia). Pola instancji, które nie mają inicjatora, będą miały prawidłową (domyślną) wartość tylko po zakończeniu wszystkich inicjatorów pól wystąpienia. Ze względu na kolejność inicjalizacji konstruktory instancji są wykonywane jako ostatnie, dlatego instancja nie jest jeszcze tworzona w momencie wykonania inicjatorów. Dlatego kompilator nie może zezwolić na odwoływanie się do żadnej właściwości wystąpienia (lub pola), zanim wystąpienie klasy zostanie w pełni skonstruowane. Dzieje się tak, ponieważ każdy dostęp do zmiennej instancji, takiej jak reminderniejawnie odwołuje się do instancji ( this), aby przekazać kompilatorowi konkretne miejsce w pamięci instancji, która ma być używana.

Jest to również powód, dla którego thisnie jest dozwolony w inicjatorze pola wystąpienia.

Inicjator zmiennej dla pola wystąpienia nie może odwoływać się do tworzonej instancji. W związku z tym jest to błąd czasu kompilacji, aby odwołać się do this w inicjatorze zmiennej, ponieważ jest to błąd czasu kompilacji dla inicjatora zmiennej, który odwołuje się do dowolnego elementu członkowskiego wystąpienia za pośrednictwem simple_name .

Jedynymi elementami członkowskimi typu, które mają być zainicjowane przed wykonaniem inicjatorów pól wystąpienia, są inicjatory pól klasy (statyczne) i konstruktory klas (statyczne) oraz metody klas. Ponieważ statyczne elementy członkowskie są niezależne od instancji, można się do nich odwoływać w dowolnym momencie:

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)];
}

Dlatego inicjatory pola wystąpienia mogą odwoływać się tylko do elementu członkowskiego klasy (statycznego elementu członkowskiego). Te reguły inicjalizacji kompilatora zapewnią deterministyczne wystąpienie typu.

Aby uzyskać więcej informacji, polecam ten dokument: Microsoft Docs: Class Declarations .

Oznacza to, że pole wystąpienia, które odwołuje się do innego elementu członkowskiego wystąpienia w celu zainicjowania jego wartości, musi zostać zainicjowane z konstruktora wystąpienia lub należy zadeklarować element członkowski, do którego się odwołuje static.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.