2017-11-21 41 views
1

Der Versuch, ein besseres Verständnis dafür zu bekommen, warum dies ist eine Sprache-Funktion:Was der Punkt des Wertes auf Nullable Types In C# ist

Wir haben:

public static DateTime? Date { get; set; } 


static void Main(string[] args) 
{ 
    Date = new DateTime(2017, 5, 5); 

    Console.WriteLine(Date.Value.Date); 
    Console.Read(); 
} 

Warum benötige ich Wert zu verwenden Nimm den Wert vom Nullable-Typ? Es ist nicht so, als ob vor dem Aufruf von Date auf Null geprüft wird. Wenn der Wert null ist, wird eine NullReference-Ausnahme ausgelöst. Ich bekomme, warum .HasValue arbeiten kann,

aber bin nicht sicher, warum wir .Wert auf jedem nulllable Typ benötigen?

+0

Äh, wie sonst würden Sie den Wert lesen? – maccettura

+0

Was schlagen Sie stattdessen vor? – Servy

+0

Warum können Sie nicht direkt in das Objekt eintauchen? –

Antwort

4

Dies ist darauf zurückzuführen, wie Nullable Types implementiert.

Die Fragezeichen-Syntax übersetzt nur in Nullable<T>, das ist eine Struktur, die Sie sehr gut selbst schreiben können (außer für die …? Syntax ist eine Sprachfunktion für diesen Typ).

Die .NET Core-Implementierung von Nullable<T> ist Open Source und its code hilft, dies zu erklären.

Nullable<T> hat nur ein boolean-Feld und ein Wertefeld des zugrunde liegenden Typs und wirft nur eine Ausnahme, wenn .Value Zugriff:

public readonly struct Nullable<T> where T : struct 
{ 
    private readonly bool hasValue; // Do not rename (binary serialization) 
    internal readonly T value; // Do not rename (binary serialization) 

    … 

    public T Value 
    { 
     get 
     { 
      if (!hasValue) 
      { 
       ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue); 
      } 
      return value; 
     } 
    } 
… 

Wenn Sie wie DateTime aDateTime = (DateTime)nullableDateTime eine gegossene/Zuordnung zu tun, dann nur Sie einen Operator rufen Dies ist in derselben Klasse definiert, die genau wie ein Operator funktioniert, der für einen benutzerdefinierten Typ definiert ist. Dieser Operator nur ruft .Value so die Besetzung den Zugriff auf das Objekt versteckt:

public static explicit operator T(Nullable<T> value) 
    { 
     return value.Value; 
    } 

Es gibt auch ein Operator für die inverse Zuordnung, so DateTime? nullableNow = DateTime.Now nennen würde:

public static implicit operator Nullable<T>(T value) 
    { 
     return new Nullable<T>(value); 
    } 
14

Lassen Sie uns zunächst die Frage klären.

In C# 1.0 hatten wir zwei breite Kategorien von Typen: Werttypen, die niemals null sind, und Referenztypen, die nullfähig sind. *

Wert und Referenztypen unterstützen einen Mitglied Zugriffsoperator, ., die einen Wert mit einer Instanz zugeordnete auswählt.

Für Referenztypen ist die Beziehung zwischen dem .-Operator und der NULL-Zulässigkeit des Empfängers: Wenn der Empfänger eine Nullreferenz ist, dann erzeugt die Verwendung des .-Operators eine Ausnahme. Da C# 1.0-Werttypen überhaupt nicht nullfähig sind, müssen Sie nicht angeben, was passiert, wenn Sie . einen Nullwerttyp eingeben. sie existieren nicht.

In C# 2.0 wurden Nullwertwerte hinzugefügt. Nullable-Werttypen sind nichts Magisches, solange ihre In-Memory-Repräsentation geht; es ist nur eine Struktur mit einer Instanz des Wertes und einem Bool, ob es null ist oder nicht.

Es gibt einige Compiler Magie (**) obwohl seit Nullable Werte Typen mit heben Semantik kommen.Unter heben wir an, wir meinen, dass Operationen auf Nullable-Werttypen die Semantik haben: "Wenn der Wert nicht null ist, dann führe die Operation auf den Wert aus und konvertiere das Ergebnis in einen nullbaren Typ; andernfalls ist das Ergebnis null". (***)

Das heißt, wenn wir

int? x = 2; 
int? y = 3; 
int? z = null; 
int? r = x + y; // nullable 5 
int? s = y + z; // null 

Hinter den Kulissen haben die Compiler alle Arten von Magie tun effizient angehoben Arithmetik zu implementieren; see my lengthy blog series on how I wrote the optimizer if this subject interests you.

Der Operator . ist jedoch nicht aufgehoben. Es könnte sein! Es gibt mindestens zwei mögliche Konstruktionen, die Sinn machen:

  1. nullable.Whatever() als Nullable-Referenztypen verhalten könnten Sie: eine Ausnahme aus, wenn der Empfänger null ist, oder
  2. es wie nullable Arithmetik verhalten könnte: wenn nullable null ist dann ist der Aufruf an Whatever() elided und das Ergebnis ist ein Null von welchem ​​Typ Whatever() zurückgegeben worden wäre.

Die Frage ist also:

Warum .Value. erfordern, wenn es ein vernünftiger Entwurf für den . Operator ist nur Arbeit und ein Mitglied des zugrunde liegenden Typs zu extrahieren?

Gut.

Beachten Sie, was ich gerade dort getan habe. Es gibt zwei Möglichkeiten, die beide vollkommen Sinn machen und mit einem etablierten, gut verstandenen Aspekt der Sprache übereinstimmen, und sie widersprechen einander. Sprachdesigner finden sich in diesem Cleft Stick die ganze Zeit. Wir befinden uns jetzt in einer Situation, in der es völlig nicht offensichtlich ist, ob sich . auf einem Nullwerttyp wie . auf einem Referenztyp verhalten sollte oder ob . sich wie + auf einem nullable int verhalten sollte. Beides ist plausibel. Welcher auch immer ausgewählt wird, jemand wird denken, dass es falsch ist.

Das Sprachen-Design-Team erwog Alternativen wie explizite. Zum Beispiel der "Elvis" ?.-Operator, der explizit ein aufgehobener Zugriff auf NULL-Member ist. Dies wurde für C# 2.0 in Betracht gezogen, aber zurückgewiesen und schließlich zu C# 6.0 hinzugefügt. Es wurden einige andere syntaktische Lösungen in Betracht gezogen, aber alle wurden aus Gründen abgelehnt, die der Geschichte entgangen waren.

Schon sehen wir, dass wir ein potenzielles Design-Minenfeld für . auf Werttypen haben, aber warten Sie, es wird schlechter.

Lassen Sie uns nun einen weiteren Aspekt der . berücksichtigen, wenn auf einen Wert Typ angewendet: Wenn der Wert Art der schreckliche wandelbaren Werttyp und das Element ist ein Feld ist, dann ist x.y eine Variable wenn x ist eine Variable und ansonsten ein Wert. Das heißt, x.y = 123 ist zulässig, wenn x eine Variable ist.Aber wenn x keine Variable ist, dann verbietet der C# -Compiler die Zuweisung, weil die Zuordnung zu einer Kopie des Werts vorgenommen würde.

Wie verhält es sich mit Nullwerttypen? Wenn wir einen Nullable-wandelbaren Werttyp X? dann was macht

x.y = 123 

tun? Denken Sie daran, x wirklich ist eine Instanz des unveränderlichen Typs Nullable<X>, also, wenn dies bedeutet, x.Value.y = 123 dann wir die Kopie des Wertes zurückgegeben werden, mutiert von der Value Eigenschaft, die sehr scheint, sehr falsch.

Was machen wir also? Sollten NULL-Werttypen selbst veränderbar sein? Wie würde diese Mutation funktionieren? Ist es eine Copy-in-Copy-out-Semantik? Das würde bedeuten, dass ref x.y illegal wäre, weil ref eine Variable verlangt, keine Eigenschaft.

Es wird eine riesige freakin 'Chaos sein.

In C# 2.0 versuchte das Designteam, Generics zu der Sprache hinzugefügt; Wenn Sie jemals versucht haben, Generika zu einem bestehenden System hinzuzufügen, wissen Sie, wie viel Arbeit das ist. Wenn Sie nicht, nun, es ist eine Menge Arbeit. Ich denke, dass das Designteam einen Pass erhalten kann, wenn es darum geht, sich für all diese Probleme zu entscheiden und . keine spezielle Bedeutung für Nullable Value-Typen zu haben. "Wenn Sie den Wert möchten, dann rufen Sie .Value" hat den Vorteil, keine besondere Arbeit seitens des Design-Teams zu erfordern! Und ähnlich, "wenn es weh tut, veränderbare Nullwerttypen zu verwenden, dann höre vielleicht damit auf", sind niedrige Kosten für die Designer.


Wenn wir in einer perfekten Welt leben, dann würden wir Arten von Typen in C# 1.0 hatten zwei orthogonal haben: Referenztypen vs Werttypen und Nullable Types vs Nicht-Nullable-Typen. Was wir erhielten, war Nullwert Referenztypen und nicht-Nullable Werttypen in C# 1.0, Nullable Werttypen in C# 2.0, und ein bisschen Null Referenz Typen in C# 8.0, ein Jahrzehnt und eine Hälfte später.

In dieser perfekten Welt hätten wir alle Operator Semantik, Aufhebung Semantik, Variable Semantik, und so weiter auf einmal aussortiert, um sie konsistent zu machen.

Aber, hey, wir leben nicht in dieser perfekten Welt. Wir leben in einer Welt, in der das Perfekte der Feind des Guten ist, und Sie müssen .Value. anstelle von . in C# 2.0 bis 5.0 und ?. in C# 6.0 sagen.


* Ich ignoriere bewusst Zeiger-Typen, die NULL-Werte zulassen und einige Eigenschaften von Werttypen haben und einige Eigenschaften von Referenztypen und die haben ihre eigenen speziellen Operatoren für dereferencing und Mitglied Zugang.

** Es gibt auch Magie in Sachen wie: Nullable Werttypen erfüllen nicht die Werttyp-Einschränkung, Nullable Werttypen Box zu Null Referenzen oder boxed zugrunde liegenden Typen und viele andere kleine spezielle Verhaltensweisen. Aber das Speicherlayout ist nichts Magisches; es ist nur ein Wert neben einem Bool.

*** Funktionelle Programmierer werden natürlich wissen, dass dies die Bindeoperation auf der vielleicht Monade ist.

+1

Oh mein, es ist wie dein Blog, aber auf SO. Vielen Dank! – VMAtm

Verwandte Themen