Wenn ich in meiner Klasse einen Bool erstelle bool check
, ist der Standardwert false.
Wenn ich denselben Bool in meiner Methode bool check
(anstelle der Klasse) erstelle, wird die Fehlermeldung "Verwendung der nicht zugewiesenen lokalen Variablenprüfung" angezeigt. Warum?
c#
language-design
local-variables
Nachime
quelle
quelle
Antworten:
Die Antworten von Yuval und David sind grundsätzlich richtig; zusammenfassen:
Ein Kommentator zu Davids Antwort fragt, warum es unmöglich ist, die Verwendung eines nicht zugewiesenen Feldes durch statische Analyse zu erkennen. Dies ist der Punkt, auf den ich in dieser Antwort näher eingehen möchte.
Zunächst ist es in der Praxis unmöglich, für eine lokale oder sonstige Variable genau zu bestimmen , ob eine Variable zugewiesen oder nicht zugewiesen ist. Erwägen:
Die Frage "ist x zugewiesen?" ist äquivalent zu "Gibt M () true zurück?" Nehmen wir nun an, M () gibt true zurück, wenn Fermats letzter Satz für alle ganzen Zahlen unter elf Gajillion wahr ist, andernfalls false. Um festzustellen, ob x definitiv zugewiesen ist, muss der Compiler im Wesentlichen einen Beweis für Fermats letzten Satz liefern. Der Compiler ist nicht so schlau.
Der Compiler implementiert stattdessen für Einheimische einen Algorithmus, der schnell ist und überschätzt, wenn ein Einheimischer nicht definitiv zugewiesen ist. Das heißt, es hat einige Fehlalarme, in denen steht "Ich kann nicht beweisen, dass dieses Lokal zugewiesen ist", obwohl Sie und ich es wissen. Beispielsweise:
Angenommen, N () gibt eine Ganzzahl zurück. Sie und ich wissen, dass N () * 0 0 sein wird, aber der Compiler weiß das nicht. (Hinweis: die C # 2.0 - Compiler tat das wissen, aber ich entfernte , dass eine Optimierung, da die Spezifikation nicht sagen , dass der Compiler das weiß.)
Also gut, was wissen wir bisher? Es ist für Einheimische unpraktisch, eine genaue Antwort zu erhalten, aber wir können die Nichtzuweisung billig überschätzen und ein ziemlich gutes Ergebnis erzielen, das auf der Seite von "Lassen Sie Ihr unklares Programm reparieren" fehlerhaft ist. Das ist gut. Warum nicht dasselbe für Felder tun? Das heißt, einen eindeutigen Zuweisungsprüfer erstellen, der billig überschätzt?
Wie viele Möglichkeiten gibt es für die Initialisierung eines lokalen Objekts? Sie kann im Text der Methode zugewiesen werden. Sie kann innerhalb eines Lambdas im Text der Methode zugewiesen werden. Dieses Lambda wird möglicherweise nie aufgerufen, daher sind diese Zuweisungen nicht relevant. Oder es kann als "out" an eine andere Methode übergeben werden. An diesem Punkt können wir davon ausgehen, dass es zugewiesen wird, wenn die Methode normal zurückkehrt. Dies sind sehr klare Punkte, an denen das Lokal zugewiesen wird, und sie befinden sich genau dort in derselben Methode, mit der das Lokal deklariert wird . Die Bestimmung der endgültigen Zuordnung für Einheimische erfordert nur eine lokale Analyse . Methoden sind in der Regel kurz - weit weniger als eine Million Codezeilen in einer Methode - und daher ist die Analyse der gesamten Methode recht schnell.
Was ist nun mit Feldern? Felder können natürlich in einem Konstruktor initialisiert werden. Oder ein Feldinitialisierer. Oder der Konstruktor kann eine Instanzmethode aufrufen, die die Felder initialisiert. Oder der Konstruktor kann eine virtuelle Methode aufrufen, die die Felder initiiert. Oder der Konstruktor kann eine Methode in einer anderen Klasse aufrufen , die sich möglicherweise in einer Bibliothek befindet und die Felder initialisiert. Statische Felder können in statischen Konstruktoren initialisiert werden. Statische Felder können von anderen statischen Konstruktoren initialisiert werden .
Im Wesentlichen kann sich der Initialisierer für ein Feld an einer beliebigen Stelle im gesamten Programm befinden , einschließlich innerhalb virtueller Methoden, die in Bibliotheken deklariert werden, die noch nicht geschrieben wurden :
Ist es ein Fehler, diese Bibliothek zu kompilieren? Wenn ja, wie soll BarCorp den Fehler beheben? Durch Zuweisen eines Standardwerts zu x? Aber genau das macht der Compiler schon.
Angenommen, diese Bibliothek ist legal. Wenn FooCorp schreibt
Ist das ein Fehler? Wie soll der Compiler das herausfinden? Die einzige Möglichkeit besteht darin, eine vollständige Programmanalyse durchzuführen , die die statische Initialisierung jedes Felds auf jedem möglichen Pfad durch das Programm verfolgt , einschließlich der Pfade, bei denen zur Laufzeit virtuelle Methoden ausgewählt werden . Dieses Problem kann beliebig schwierig sein ; Es kann die simulierte Ausführung von Millionen von Steuerpfaden beinhalten. Die Analyse lokaler Kontrollflüsse dauert Mikrosekunden und hängt von der Größe der Methode ab. Die Analyse globaler Kontrollflüsse kann Stunden dauern, da dies von der Komplexität jeder Methode im Programm und aller Bibliotheken abhängt .
Warum also nicht eine billigere Analyse durchführen, bei der nicht das gesamte Programm analysiert werden muss und die nur noch stärker überschätzt wird? Schlagen Sie einen funktionierenden Algorithmus vor, der es nicht zu schwierig macht, ein korrektes Programm zu schreiben, das tatsächlich kompiliert wird, und das Designteam kann dies berücksichtigen. Ich kenne keinen solchen Algorithmus.
Nun schlägt der Kommentator vor, "dass ein Konstruktor alle Felder initialisieren muss". Das ist keine schlechte Idee. Tatsächlich ist es eine nicht schlechte Idee, dass C # diese Funktion bereits für Strukturen hat . Ein Strukturkonstruktor ist erforderlich, um alle Felder definitiv zuzuweisen, bis der ctor normal zurückkehrt. Der Standardkonstruktor initialisiert alle Felder mit ihren Standardwerten.
Was ist mit Klassen? Woher wissen Sie, dass ein Konstruktor ein Feld initialisiert hat ? Der ctor könnte eine virtuelle Methode zum Initialisieren der Felder aufrufen , und jetzt befinden wir uns wieder an der Position, an der wir uns zuvor befanden. Strukturen haben keine abgeleiteten Klassen; Klassen könnten. Ist eine Bibliothek mit einer abstrakten Klasse erforderlich, um einen Konstruktor zu enthalten, der alle seine Felder initialisiert? Woher weiß die abstrakte Klasse, mit welchen Werten die Felder initialisiert werden sollen?
John schlägt vor, das Aufrufen von Methoden in einem Ctor einfach zu verbieten, bevor die Felder initialisiert werden. Zusammenfassend sind unsere Optionen also:
Das Designteam entschied sich für die dritte Option.
quelle
bool x;
gleichbool x = false;
innerhalb einer Methode sein ?Weil der Compiler versucht, Sie daran zu hindern, einen Fehler zu machen.
Wird Ihre Variable initialisiert,
false
um etwas in diesem bestimmten Ausführungspfad zu ändern? Wahrscheinlich nicht, wenndefault(bool)
man bedenkt , dass es sowieso falsch ist, aber es zwingt Sie, sich dessen bewusst zu sein, dass dies geschieht. Die .NET-Umgebung verhindert, dass Sie auf "Garbage Memory" zugreifen, da alle Werte auf ihren Standardwert initialisiert werden. Stellen Sie sich dennoch vor, dies sei ein Referenztyp, und Sie würden einen nicht initialisierten (null) Wert an eine Methode übergeben, die eine Nicht-Null erwartet, und zur Laufzeit eine NRE erhalten. Der Compiler versucht lediglich, dies zu verhindern, und akzeptiert die Tatsache, dass dies manchmal zubool b = false
Anweisungen führen kann.Eric Lippert spricht darüber in einem Blogbeitrag :
Warum gilt das nicht für ein Klassenfeld? Nun, ich gehe davon aus, dass die Linie irgendwo gezogen werden musste und die Initialisierung lokaler Variablen im Gegensatz zu Klassenfeldern viel einfacher zu diagnostizieren und richtig zu machen ist. Der Compiler könnte dies tun, aber denken Sie an alle möglichen Überprüfungen, die er durchführen müsste (wobei einige davon unabhängig vom Klassencode selbst sind), um zu bewerten, ob jedes Feld in einer Klasse initialisiert ist. Ich bin kein Compiler - Designer, aber ich bin sicher , dass es auf jeden Fall sein schwieriger , da es viele Fälle gibt, die berücksichtigt werden, und hat in einem getan werden rechtzeitig auch. Für jede Funktion, die Sie entwerfen, schreiben, testen und bereitstellen müssen, wäre der Wert der Implementierung im Gegensatz zum Aufwand nicht wert und kompliziert.
quelle
Die kurze Antwort lautet, dass Code, der auf nicht initialisierte lokale Variablen zugreift, vom Compiler mithilfe einer statischen Analyse zuverlässig erkannt werden kann. Bei Feldern ist dies nicht der Fall. Der Compiler erzwingt also den ersten Fall, aber nicht den zweiten.
Dies ist nicht mehr als eine Entwurfsentscheidung der C # -Sprache, wie von Eric Lippert erklärt . Die CLR und die .NET-Umgebung erfordern dies nicht. VB.NET kompiliert beispielsweise problemlos mit nicht initialisierten lokalen Variablen, und in Wirklichkeit initialisiert die CLR alle nicht initialisierten Variablen auf Standardwerte.
Das gleiche könnte mit C # passieren, aber die Sprachdesigner entschieden sich dagegen. Der Grund dafür ist, dass initialisierte Variablen eine große Fehlerquelle darstellen. Durch die Anforderung der Initialisierung hilft der Compiler, versehentliche Fehler zu reduzieren.
Warum findet diese obligatorische explizite Initialisierung nicht bei Feldern innerhalb einer Klasse statt? Einfach, weil diese explizite Initialisierung während der Erstellung auftreten kann, indem eine Eigenschaft von einem Objektinitialisierer oder sogar von einer Methode aufgerufen wird, die lange nach dem Ereignis aufgerufen wird. Der Compiler kann keine statische Analyse verwenden, um festzustellen, ob jeder mögliche Pfad durch den Code dazu führt, dass die Variable explizit vor uns initialisiert wird. Es wäre ärgerlich, etwas falsch zu machen, da dem Entwickler möglicherweise gültiger Code übrig bleibt, der nicht kompiliert werden kann. C # erzwingt dies also überhaupt nicht und die CLR initialisiert Felder automatisch auf einen Standardwert, wenn sie nicht explizit festgelegt wird.
Die Durchsetzung der lokalen Variableninitialisierung durch C # ist begrenzt, was Entwickler häufig auffängt. Betrachten Sie die folgenden vier Codezeilen:
Die zweite Codezeile wird nicht kompiliert, da versucht wird, eine nicht initialisierte Zeichenfolgenvariable zu lesen. Die vierte Codezeile wird zwar wie
array
initialisiert problemlos kompiliert , jedoch nur mit Standardwerten. Da der Standardwert einer Zeichenfolge null ist, erhalten wir zur Laufzeit eine Ausnahme. Jeder, der hier Zeit mit Stapelüberlauf verbracht hat, wird wissen, dass diese explizite / implizite Initialisierungsinkonsistenz zu sehr vielen "Warum erhalte ich den Fehler" Objektreferenz nicht auf eine Instanz eines Objekts festgelegt "führt?" Fragen.quelle
public interface I1 { string str {get;set;} }
und eine Methodeint f(I1 value) { return value.str.Length; }
. Wenn dies in einer Bibliothek vorhanden ist, kann der Compiler nicht wissen, womit diese Bibliothek verknüpft ist. Daherset
wird derget
Hintergrund möglicherweise nicht explizit initialisiert, er muss jedoch solchen Code kompilieren.f
. Es würde beim Kompilieren der Konstruktoren generiert. Wenn Sie einen Konstruktor mit einem möglicherweise nicht initialisierten Feld belassen, ist dies ein Fehler. Möglicherweise müssen auch Einschränkungen beim Aufrufen von Klassenmethoden und Gettern vorgenommen werden, bevor alle Felder initialisiert werden.Gute Antworten oben, aber ich dachte, ich würde eine viel einfachere / kürzere Antwort veröffentlichen, damit die Leute faul sind, eine lange zu lesen (wie ich).
Klasse
Die Eigenschaft wurde
Boo
möglicherweise im Konstruktor initialisiert oder nicht . Wenn esreturn Boo;
also gefunden wird, wird nicht davon ausgegangen, dass es initialisiert wurde. Es unterdrückt einfach den Fehler.Funktion
Die
{ }
Zeichen definieren den Umfang eines Codeblocks. Der Compiler geht durch die Zweige dieser{ }
Blöcke und verfolgt die Dinge. Es kann leicht festgestellt werden , dassBoo
nicht initialisiert wurde. Der Fehler wird dann ausgelöst.Warum liegt der Fehler vor?
Der Fehler wurde eingeführt, um die Anzahl der Codezeilen zu verringern, die erforderlich sind, um den Quellcode sicher zu machen. Ohne den Fehler würde das obige so aussehen.
Aus dem Handbuch:
Referenz: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx
quelle