Wir haben festgestellt, dass viele Fehler in unserer in C # (oder Java) entwickelten Software eine NullReferenceException verursachen.
Gibt es einen Grund, warum "null" überhaupt in die Sprache aufgenommen wurde?
Wenn es keine "Null" gäbe, hätte ich doch keinen Fehler, oder?
Mit anderen Worten, welche Funktion in der Sprache könnte ohne Null nicht funktionieren?
Antworten:
Anders Hejlsberg, "C # Vater", hat gerade in seinem Computerworld-Interview über diesen Punkt gesprochen :
Cyrus Najmabadi, ein ehemaliger Software-Design-Ingenieur im C # -Team (der jetzt bei Google arbeitet), diskutiert dieses Thema in seinem Blog: ( 1. , 2. , 3. , 4. ). Es scheint, dass das größte Hindernis für die Einführung von nicht nullbaren Typen darin besteht, dass die Notation die Gewohnheiten und die Codebasis der Programmierer stören würde. Etwa 70% der Referenzen von C # -Programmen werden wahrscheinlich als nicht nullfähige Programme enden.
Wenn Sie wirklich einen nicht nullbaren Referenztyp in C # haben möchten, sollten Sie versuchen, Spec # zu verwenden , eine C # -Erweiterung, die die Verwendung von "!" als nicht nullbares Zeichen.
static string AcceptNotNullObject(object! s) { return s.ToString(); }
quelle
Die Nichtigkeit ist eine natürliche Folge von Referenztypen. Wenn Sie eine Referenz haben, muss diese auf ein Objekt verweisen - oder null sein. Wenn Sie die Nichtigkeit verbieten würden, müssten Sie immer sicherstellen, dass jede Variable mit einem Nicht-Null-Ausdruck initialisiert wurde - und selbst dann hätten Sie Probleme, wenn Variablen während der Initialisierungsphase gelesen würden.
Wie würden Sie vorschlagen, das Konzept der Nichtigkeit zu entfernen?
quelle
Wie viele Dinge in der objektorientierten Programmierung geht alles auf ALGOL zurück. Tony Hoare nannte es einfach seinen "Milliarden-Dollar-Fehler". Wenn überhaupt, ist das eine Untertreibung.
Hier ist eine wirklich interessante These, wie man die Nullfähigkeit in Java nicht zum Standard macht. Die Parallelen zu C # sind offensichtlich.
quelle
Null in C # ist meistens eine Übertragung von C ++, die Zeiger hatte, die auf nichts im Speicher (oder besser gesagt auf eine Adresse
0x00
) zeigten . In diesem Interview sagt Anders Hejlsberg, dass er nicht nullbare Referenztypen in C # hinzugefügt hätte.Null hat jedoch auch einen legitimen Platz in einem Typsystem, ähnlich dem unteren Typ (wo
object
ist der obere Typ). In lisp ist der untere TypNIL
und in Scala ist esNothing
.Es wäre schon möglich gewesen C # ohne NULL - Werte zu entwerfen , aber dann würden Sie mit einer akzeptablen Lösung für die Verwendungen haben zu kommen , dass die Menschen in der Regel für haben
null
, wieunitialized-value
,not-found
,default-value
,undefined-value
, undNone<T>
. Es hätte wahrscheinlich weniger Akzeptanz bei C ++ - und Java-Programmierern gegeben, wenn ihnen dies trotzdem gelungen wäre. Zumindest bis sie sahen, dass C # -Programme niemals Nullzeigerausnahmen hatten.quelle
Das Entfernen von Null würde nicht viel lösen. Für die meisten Variablen, die auf init festgelegt sind, benötigen Sie eine Standardreferenz. Anstelle von Nullreferenzausnahmen würden Sie unerwartetes Verhalten erhalten, da die Variable auf die falschen Objekte zeigt. Zumindest Nullreferenzen schlagen schnell fehl, anstatt unerwartetes Verhalten zu verursachen.
Sie können sich das Nullobjektmuster ansehen, um einen Teil dieses Problems zu lösen
quelle
Null ist eine äußerst leistungsstarke Funktion. Was machen Sie, wenn Sie keinen Wert haben? Es ist NULL!
Eine Denkrichtung ist, niemals null zurückzugeben, eine andere ist immer. Einige sagen beispielsweise, Sie sollten ein gültiges, aber leeres Objekt zurückgeben.
Ich bevorzuge null, da es für mich ein wahrer Hinweis darauf ist, was es tatsächlich ist. Wenn ich eine Entität nicht aus meiner Persistenzschicht abrufen kann, möchte ich null. Ich möchte keinen leeren Wert. Aber das bin ich.
Es ist besonders praktisch bei Grundelementen. Zum Beispiel, wenn ich wahr oder falsch habe, es aber in einem Sicherheitsformular verwendet wird, in dem eine Berechtigung Zulassen, Verweigern oder Nicht festgelegt sein kann. Nun, ich möchte, dass das nicht null ist. Also kann ich bool benutzen?
Es gibt noch viel mehr, worüber ich weitermachen könnte, aber ich werde es dort lassen.
quelle
Die Antwort lautet NEIN . Das Problem ist nicht, dass C # null zulässt, das Problem ist, dass Sie Fehler haben, die sich zufällig mit der NullReferenceException manifestieren. Wie bereits erwähnt, haben Nullen in der Sprache den Zweck, entweder einen "leeren" Referenztyp oder einen Nichtwert (leer / nichts / unbekannt) anzugeben.
quelle
Null verursacht keine NullPointerExceptions ...
Programmierer verursachen NullPointerExceptions.
Ohne Nullen verwenden wir wieder einen tatsächlichen beliebigen Wert, um festzustellen, dass der Rückgabewert einer Funktion oder Methode ungültig war. Sie müssen immer noch nach dem zurückgegebenen -1 (oder was auch immer) suchen. Das Entfernen von Nullen löst die Faulheit nicht auf magische Weise, sondern verschleiert sie mürrisch.
quelle
Die Frage kann interpretiert werden als "Ist es besser, einen Standardwert für jeden Referenztyp (wie String.Empty) oder null zu haben?". In dieser Perspektive würde ich es vorziehen, Nullen zu haben, weil;
quelle
"Null" ist in der Sprache enthalten, da wir Werttypen und Referenztypen haben. Es ist wahrscheinlich ein Nebeneffekt, aber ein guter, denke ich. Es gibt uns viel Macht darüber, wie wir den Speicher effektiv verwalten.
Warum haben wir null? ...
Werttypen werden auf dem "Stapel" gespeichert, ihr Wert befindet sich direkt in diesem Speicher (dh int x = 5 bedeutet, dass der Speicherort für diese Variable "5" enthält).
Referenztypen hingegen haben einen "Zeiger" auf dem Stapel, der auf den tatsächlichen Wert auf dem Heap zeigt (dh Zeichenfolge x = "ello" bedeutet, dass der Speicherblock auf dem Stapel nur eine Adresse enthält, die auf den tatsächlichen Wert auf dem Heap zeigt ).
Ein Nullwert bedeutet einfach, dass unser Wert auf dem Stapel nicht auf einen tatsächlichen Wert auf dem Heap verweist - es ist ein leerer Zeiger.
Hoffe ich habe das gut genug erklärt.
quelle
Wenn Sie eine 'NullReferenceException' erhalten, verweisen Sie möglicherweise weiterhin auf Objekte, die nicht mehr vorhanden sind. Dies ist kein Problem mit 'null', sondern ein Problem mit Ihrem Code, der auf nicht vorhandene Adressen verweist.
quelle
Es gibt Situationen, in denen null ein guter Weg ist, um anzuzeigen, dass eine Referenz nicht initialisiert wurde. Dies ist in einigen Szenarien wichtig.
Zum Beispiel:
MyResource resource; try { resource = new MyResource(); // // Do some work // } finally { if (resource != null) resource.Close(); }
Dies wird in den meisten Fällen durch die Verwendung einer using- Anweisung erreicht. Aber das Muster ist immer noch weit verbreitet.
In Bezug auf Ihre NullReferenceException lässt sich die Ursache solcher Fehler häufig leicht reduzieren, indem ein Codierungsstandard implementiert wird, bei dem alle Parameter auf ihre Gültigkeit überprüft werden. Abhängig von der Art des Projekts finde ich, dass es in den meisten Fällen ausreicht, Parameter für exponierte Mitglieder zu überprüfen. Wenn die Parameter nicht innerhalb des erwarteten Bereichs liegen, wird abhängig vom verwendeten Fehlerbehandlungsmuster eine ArgumentException ausgelöst oder ein Fehlerergebnis zurückgegeben.
Die Parameterprüfung entfernt an sich keine Fehler, aber auftretende Fehler sind während der Testphase leichter zu finden und zu korrigieren.
Als Hinweis hat Anders Hejlsberg das Fehlen einer Nicht-Null-Durchsetzung als einen der größten Fehler in der C # 1.0-Spezifikation erwähnt, und dass es "schwierig" ist, es jetzt aufzunehmen.
Wenn Sie immer noch der Meinung sind, dass ein statisch erzwungener Referenzwert ungleich Null von großer Bedeutung ist, können Sie die Spezifikationssprache überprüfen . Es ist eine Erweiterung von C #, bei der Nicht-Null-Referenzen Teil der Sprache sind. Dies stellt sicher, dass einer als nicht null gekennzeichneten Referenz niemals eine Nullreferenz zugewiesen werden kann.
quelle
In einer Antwort wurde erwähnt, dass Datenbanken Nullen enthalten. Das stimmt, aber sie unterscheiden sich sehr von Nullen in C #.
In C # sind Nullen Markierungen für eine Referenz, die sich auf nichts bezieht.
In Datenbanken sind Nullen Marker für Wertzellen, die keinen Wert enthalten. Mit Wertzellen meine ich im Allgemeinen den Schnittpunkt einer Zeile und einer Spalte in einer Tabelle, aber das Konzept der Wertzellen könnte über Tabellen hinaus erweitert werden.
Der Unterschied zwischen den beiden scheint auf den ersten Blick trivial. Aber es ist nicht.
quelle
Null, wie es in C # / C ++ / Java / Ruby verfügbar ist, wird am besten als eine Kuriosität einer obskuren Vergangenheit (Algol) angesehen, die irgendwie bis heute überlebt hat.
Sie verwenden es auf zwei Arten:
Wie Sie vermutet haben, ist 1) das, was uns endlose Probleme in gängigen imperativen Sprachen verursacht und vor langer Zeit hätte verboten werden müssen. 2) ist das wahre wesentliche Merkmal.
Es gibt Sprachen, die 1) vermeiden, ohne 2) zu verhindern.
Zum Beispiel ist OCaml eine solche Sprache.
Eine einfache Funktion, die eine immer höher werdende Ganzzahl ab 1 zurückgibt:
let counter = ref 0;; let next_counter_value () = (counter := !counter + 1; !counter);;
Und in Bezug auf die Optionalität:
type distributed_computation_result = NotYetAvailable | Result of float;; let print_result r = match r with | Result(f) -> Printf.printf "result is %f\n" f | NotYetAvailable -> Printf.printf "result not yet available\n";;
quelle
Ich kann nicht mit Ihrem speziellen Problem sprechen, aber es scheint, dass das Problem nicht die Existenz von Null ist. In Datenbanken ist Null vorhanden. Sie benötigen eine Möglichkeit, dies auf Anwendungsebene zu berücksichtigen. Ich glaube nicht, dass dies der einzige Grund ist, warum es in .net existiert, wohlgemerkt. Aber ich denke, das ist einer der Gründe.
quelle
NULL
normalerweise alsDBNull.Value
anstelle von dargestelltnull
.Ich bin überrascht, dass niemand über Datenbanken für ihre Antwort gesprochen hat. Datenbanken haben nullfähige Felder, und jede Sprache, die Daten von einer Datenbank empfängt, muss damit umgehen. Das bedeutet, einen Nullwert zu haben.
Tatsächlich ist dies so wichtig, dass Sie Basistypen wie int auf Null setzen können!
Berücksichtigen Sie auch Rückgabewerte von Funktionen. Was wäre, wenn eine Funktion ein paar Zahlen teilen soll und der Nenner 0 sein könnte? Die einzige "richtige" Antwort in einem solchen Fall wäre null. (Ich weiß, in einem so einfachen Beispiel wäre eine Ausnahme wahrscheinlich die bessere Option ... aber es kann Situationen geben, in denen alle Werte korrekt sind, aber gültige Daten zu einer ungültigen oder nicht kalkulierbaren Antwort führen können. Ich bin mir nicht sicher, ob in solchen Fällen eine Ausnahme verwendet werden sollte Fälle...)
quelle
Neben ALLEN bereits erwähnten Gründen wird NULL benötigt, wenn Sie einen Platzhalter für ein noch nicht erstelltes Objekt benötigen. Zum Beispiel. Wenn Sie einen Zirkelverweis zwischen zwei Objekten haben, benötigen Sie null, da Sie nicht beide gleichzeitig instanziieren können.
class A { B fieldb; } class B { A fielda; } A a = new A() // a.fieldb is null B b = new B() { fielda = a } // b.fielda isnt a.fieldb = b // now it isnt null anymore
Bearbeiten: Möglicherweise können Sie eine Sprache herausziehen, die ohne Nullen funktioniert, aber es wird definitiv keine objektorientierte Sprache sein. Zum Beispiel hat Prolog keine Nullwerte.
quelle
Wenn Sie ein Objekt mit einer Instanzvariablen erstellen, die auf ein Objekt verweist, welchen Wert haben Sie für diese Variable, bevor Sie ihr eine Objektreferenz zugewiesen haben?
quelle
Ich schlage vor:
quelle
Häufig - NullReferenceException bedeutet, dass einigen Methoden die Übergabe nicht gefallen hat und eine Nullreferenz zurückgegeben wurde, die später verwendet wurde, ohne die Referenz vor der Verwendung zu überprüfen.
Diese Methode hätte eine detailliertere Ausnahme auslösen können, anstatt null zurückzugeben, was der ausfallsicheren Denkweise entspricht.
Oder die Methode gibt aus praktischen Gründen möglicherweise null zurück, sodass Sie schreiben können, wenn Sie nicht versuchen , den "Overhead" einer Ausnahme zu vermeiden.
quelle
Ob dies Fälle von Gebrauch oder Missbrauch sind, ist subjektiv, aber ich benutze sie manchmal.
quelle
Es tut mir leid, dass ich vier Jahre zu spät geantwortet habe. Ich bin erstaunt, dass bisher keine der Antworten die ursprüngliche Frage folgendermaßen beantwortet hat:
Sprachen wie C # und Java , wie C und andere Sprachen vor ihnen, haben
null
es dem Programmierer ermöglicht , schnellen, optimierten Code zu schreiben, indem er Zeiger auf effiziente Weise verwendet.Zuerst ein bisschen Geschichte. Der Grund, warum
null
erfunden wurde, ist für die Effizienz. Wenn Sie in Assembly auf niedriger Ebene programmieren, gibt es keine Abstraktion, Sie haben Werte in Registern und Sie möchten das Beste daraus machen. Die Definition von Null als ungültigen Zeigerwert ist eine hervorragende Strategie, um entweder ein Objekt oder nichts darzustellen .Warum sollten Sie die meisten möglichen Werte eines einwandfreien Speicherworts verschwenden, wenn Sie das optionale Wertemuster schnell und ohne Speicheraufwand implementieren können ? Deshalb
null
ist es so nützlich.Semantisch
null
ist es für Programmiersprachen in keiner Weise notwendig. In klassischen Funktionssprachen wie Haskell oder in der ML-Familie gibt es beispielsweise keine Null, sondern Typen mit dem Namen "Vielleicht" oder "Option". Sie stellen das übergeordnete Konzept des optionalen Werts dar, ohne sich in irgendeiner Weise darum zu kümmern, wie der generierte Assemblycode aussehen wird (das ist die Aufgabe des Compilers).Dies ist auch sehr nützlich, da der Compiler dadurch mehr Fehler abfangen kann und dies weniger NullReferenceExceptions bedeutet.
Im Gegensatz zu diesen Programmiersprachen auf sehr hoher Ebene erlauben C # und Java für jeden Referenztyp einen möglichen Wert von null (dies ist ein anderer Name für den Typ, der am Ende mithilfe von Zeigern implementiert wird ).
Dies mag schlecht erscheinen, aber das Gute daran ist, dass der Programmierer das Wissen darüber, wie es unter der Haube funktioniert, nutzen kann, um effizienteren Code zu erstellen (obwohl die Sprache über eine Garbage Collection verfügt).
Dies ist der Grund, warum
null
es heutzutage immer noch Sprachen gibt: ein Kompromiss zwischen der Notwendigkeit eines allgemeinen Konzepts des optionalen Werts und dem allgegenwärtigen Bedürfnis nach Effizienz.quelle
Die Funktion, die ohne null nicht funktionieren könnte, kann "das Fehlen eines Objekts" darstellen.
Das Fehlen eines Objekts ist ein wichtiges Konzept. In der objektorientierten Programmierung benötigen wir sie, um eine optionale Zuordnung zwischen Objekten darzustellen: Objekt A kann an ein Objekt B angehängt werden, oder A hat möglicherweise kein Objekt B. Ohne Null könnten wir dies dennoch emulieren: zum Beispiel Wir könnten eine Liste von Objekten verwenden, um B mit A zu verknüpfen. Diese Liste könnte ein Element (ein B) enthalten oder leer sein. Dies ist etwas unpraktisch und löst nichts wirklich. Code, der davon ausgeht, dass es ein B gibt, wie
aobj.blist.first().method()
es in ähnlicher Weise wie eine Nullreferenzausnahme explodiert: (Wennblist
es leer ist, wie verhält es sichblist.first()
?)Apropos Listen: Mit null können Sie eine verknüpfte Liste beenden. A
ListNode
kann einen Verweis auf einen anderen enthalten,ListNode
der null sein kann. Gleiches gilt für andere dynamische Mengenstrukturen wie Bäume. Mit Null können Sie einen normalen Binärbaum haben, dessen Blattknoten durch untergeordnete Referenzen gekennzeichnet sind, die null sind.Listen und Bäume können ohne Null erstellt werden, müssen jedoch kreisförmig oder unendlich / faul sein. Dies wird wahrscheinlich von den meisten Programmierern als inakzeptable Einschränkung angesehen, die es vorziehen würden, beim Entwerfen von Datenstrukturen die Wahl zu haben.
Die mit Nullreferenzen verbundenen Schmerzen, wie Nullreferenzen, die versehentlich aufgrund von Fehlern auftreten und Ausnahmen verursachen, sind teilweise eine Folge des statischen Typsystems, das in jeden Typ einen Nullwert einführt: Es gibt einen Null-String, eine Null-Ganzzahl, ein Null-Widget, ...
In einer dynamisch typisierten Sprache kann es ein einzelnes Nullobjekt geben, das einen eigenen Typ hat. Das Ergebnis ist, dass Sie alle Darstellungsvorteile von Null sowie mehr Sicherheit haben. Wenn Sie beispielsweise eine Methode schreiben, die einen String-Parameter akzeptiert, ist garantiert, dass der Parameter ein String-Objekt und nicht null ist. In der String-Klasse gibt es keine Nullreferenz: Etwas, von dem bekannt ist, dass es ein String ist, kann nicht das Objekt null sein. Referenzen haben keinen Typ in einer dynamischen Sprache. Ein Speicherort wie ein Klassenmitglied oder ein Funktionsparameter enthält einen Wert, der eine Referenz auf ein Objekt sein kann. Dieses Objekt hat den Typ, nicht die Referenz.
Diese Sprachen bieten also ein sauberes, mehr oder weniger matematisch reines Modell von "Null", und die statischen verwandeln es dann in ein Frankenstein-Monster.
quelle
Wenn ein Framework die Erstellung eines Arrays eines bestimmten Typs ermöglicht, ohne anzugeben, was mit den neuen Elementen geschehen soll, muss dieser Typ einen Standardwert haben. Für Typen, die eine veränderbare Referenzsemantik (*) implementieren, gibt es im Allgemeinen keinen sinnvollen Standardwert. Ich halte es für eine Schwäche des .NET-Frameworks, dass es keine Möglichkeit gibt, anzugeben, dass ein nicht virtueller Funktionsaufruf eine Nullprüfung unterdrücken soll. Dies würde es unveränderlichen Typen wie String ermöglichen, sich als Werttypen zu verhalten, indem sinnvolle Werte für Eigenschaften wie Länge zurückgegeben werden.
(*) Beachten Sie, dass in VB.NET und C # veränderbare Referenzsemantiken entweder durch Klassen- oder Strukturtypen implementiert werden können. Ein Strukturtyp würde eine veränderbare Referenzsemantik implementieren, indem er als Proxy für eine umschlossene Instanz eines Klassenobjekts fungiert, auf das er eine unveränderliche Referenz enthält.
Es wäre auch hilfreich, wenn man angeben könnte, dass eine Klasse eine nicht nullbare veränderbare Semantik vom Werttyp haben soll (was bedeutet, dass durch das Instanziieren eines Feldes dieses Typs zumindest eine neue Objektinstanz unter Verwendung eines Standardkonstruktors erstellt wird, und dass Durch Kopieren eines Felds dieses Typs wird eine neue Instanz erstellt, indem die alte kopiert wird (rekursives Behandeln verschachtelter Werttypklassen).
Es ist jedoch unklar, wie viel Unterstützung genau in den Rahmen dafür eingebaut werden sollte. Wenn das Framework selbst die Unterschiede zwischen veränderlichen Werttypen, veränderlichen Referenztypen und unveränderlichen Typen erkennt, können Klassen, die selbst Verweise auf eine Mischung aus veränderlichen und unveränderlichen Typen von externen Klassen enthalten, effizient vermeiden, unnötige Kopien tief unveränderlicher Objekte zu erstellen.
quelle
Null ist eine wesentliche Voraussetzung für jede OO-Sprache. Jede Objektvariable, der keine Objektreferenz zugewiesen wurde, muss null sein.
quelle