Ein Feldinitialisierer kann nicht auf das nicht statische Feld, die Methode oder die Eigenschaft verweisen


90

Ich habe eine Klasse und wenn ich versuche, sie in einer anderen Klasse zu verwenden, erhalte ich den folgenden Fehler.

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

    }
}

Verwenden der Klasse in einer anderen Klasse

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

Fehler (CS0236):

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

Warum passiert es und wie kann man es beheben?

Antworten:


143

Diese Linie:

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

Sie können keine Instanzvariable verwenden, um eine andere Instanzvariable zu initialisieren . Warum? Da der Compiler diese neu anordnen kann, gibt es keine Garantie, reminderdie zuvor initialisiert wird defaultReminder, sodass die obige Zeile möglicherweise a auslöstNullReferenceException .

Verwenden Sie stattdessen einfach:

private dynamic defaultReminder = TimeSpan.FromMinutes(15);

Alternativ können Sie den Wert im Konstruktor einrichten:

private dynamic defaultReminder;

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

Weitere Details zu diesem Compilerfehler finden Sie in MSDN - Compilerfehler CS0236 .


3
Java ist für diese Art von Konstrukten eher "verzeihend". Ich weiß nicht, ob das eine gute Sache ist. stackoverflow.com/questions/1494735/…
Wouter Schut

31
Nein, der Compiler kann die Initialisierer nicht neu anordnen. In der C # -Sprachenspezifikation wird im Abschnitt "10.5.5.2 Instanzfeldinitialisierung" Folgendes angegeben: Die Variableninitialisierer werden in der Textreihenfolge ausgeführt, in der sie in der Klassendeklaration erscheinen. Dies wird sogar in "10.11.2 Instanzvariableninitialisierer" wiederholt, wo sie sagen: Die Variableninitialisierer werden in der Textreihenfolge ausgeführt, in der sie in der Klassendeklaration erscheinen. Ihre Erklärung ist also falsch. Die Reihenfolge ist festgelegt. Der Grund, warum es nicht erlaubt ist, ist, dass die Designer von C # es so wollten.
Jeppe Stig Nielsen

(Nur im Fall von a partial classmit "Teilen" in mehreren Dateien ist die Reihenfolge der Feldinitialisierer unklar, aber das gilt auch für staticFelder!)
Jeppe Stig Nielsen

@WouterSchut Der Thread, den Sie verlinken, handelt nicht von Java?! Es geht auch um C #, jedoch mit staticFeldern anstelle von Instanzfeldern.
Jeppe Stig Nielsen

2
@ Andrew Stimmt überhaupt nicht. Viele Entscheidungen werden getroffen, um schlechte Praktiken zu verbieten. Obwohl sie theoretisch implementiert werden können, sind einige durch Warnungen geschützt und andere sind einfache Fehler. und ich denke, dass dies einer dieser Fälle ist ... obwohl der Standard sagt, dass er sequenziell ist, würde selbst ein erfahrener Entwickler es nicht mit Zuversicht sagen (ohne den Standard zu durchsuchen).
Tomer W

21

Sie müssen diesen Code in den Konstruktor Ihrer Klasse einfügen:

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

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

Der Grund ist, dass Sie nicht eine Instanzvariable verwenden können, um eine andere mithilfe eines Feldinitialisierers zu initialisieren.


9

Sie können so verwenden

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

10
Willkommen bei Stack Overflow! Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich dabei, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen. Bitte versuchen Sie auch, Ihren Code nicht mit erklärenden Kommentaren zu überfüllen, da dies die Lesbarkeit sowohl des Codes als auch der Erklärungen beeinträchtigt!
Jmattheis

3
Es verwendet => anstelle von = und macht es so zu einer Eigenschaft.
Vincent

2
Seien Sie vorsichtig mit dieser Technik, da die Verwendung =>nicht den tatsächlichen Wert festlegt, sondern den Code bei jedem defaultReminderZugriff ausführt . Dies ist möglicherweise nicht beabsichtigt und
wirkt sich

4

private dynamic defaultReminder = reminder.TimeSpanText[TimeSpan.FromMinutes(15)];ist ein Feldinitialisierer und wird zuerst ausgeführt (bevor ein Feld ohne Initialisierer auf seinen Standardwert gesetzt wird und bevor der aufgerufene Instanzkonstruktor ausgeführt wird). Instanzfelder ohne Initialisierer haben erst einen legalen (Standard-) Wert, nachdem alle Instanzfeldinitialisierer abgeschlossen sind. Aufgrund der Initialisierungsreihenfolge werden Instanzkonstruktoren zuletzt ausgeführt, weshalb die Instanz zum Zeitpunkt der Ausführung der Initialisierer noch nicht erstellt wurde. Daher kann der Compiler nicht zulassen, dass auf eine Instanzeigenschaft (oder ein Feld) verwiesen wird, bevor die Klasseninstanz vollständig erstellt wurde. Dies liegt daran, dass jeder Zugriff auf eine Instanzvariable wie reminderimplizit auf die Instanz ( this) verweist , um dem Compiler den konkreten Speicherort der zu verwendenden Instanz mitzuteilen.

Dies ist auch der Grund, warum thisdies in einem Instanzfeldinitialisierer nicht zulässig ist.

Ein Variableninitialisierer für ein Instanzfeld kann nicht auf die zu erstellende Instanz verweisen. Daher ist es ein Fehler zur Kompilierungszeit, dies in einem Variableninitialisierer zu referenzieren, da es ein Fehler zur Kompilierungszeit für einen Variableninitialisierer ist, ein Instanzmitglied über einen einfachen Namen zu referenzieren .

Die einzigen Typelemente, die garantiert initialisiert werden, bevor Instanzfeldinitialisierer ausgeführt werden, sind Klassen- (statische) Feldinitialisierer und Klassenkonstruktoren (statische Konstruktoren) und Klassenmethoden. Da statische Elemente instanzunabhängig sind, können sie jederzeit referenziert werden:

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

Aus diesem Grund dürfen Instanzfeldinitialisierer nur auf ein Klassenmitglied (statisches Mitglied) verweisen. Diese Compiler-Initialisierungsregeln stellen eine deterministische Typinstanziierung sicher.

Für weitere Details empfehle ich dieses Dokument: Microsoft Docs: Klassendeklarationen .

Dies bedeutet, dass ein Instanzfeld, das auf ein anderes Instanzmitglied verweist, um seinen Wert zu initialisieren, vom Instanzkonstruktor initialisiert oder das referenzierte Mitglied deklariert werden muss static.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.