it-swarm.com.de

Nicht nullbare C # 8-Referenzen und das Try-Muster

In C # -Klassen gibt es ein Muster, das durch Dictionary.TryGetValue Und int.TryParse Veranschaulicht wird: eine Methode, die einen Booleschen Wert zurückgibt, der den Erfolg einer Operation anzeigt, und einen out-Parameter, der das tatsächliche Ergebnis enthält; Wenn die Operation fehlschlägt, wird der Parameter out auf null gesetzt.

Nehmen wir an, ich verwende nicht nullbare C # 8-Referenzen und möchte eine TryParse-Methode für meine eigene Klasse schreiben. Die richtige Signatur lautet:

public static bool TryParse(string s, out MyClass? result);

Da das Ergebnis im falschen Fall null ist, muss die Variable out als nullable markiert werden.

Das Try-Muster wird jedoch im Allgemeinen folgendermaßen verwendet:

if (MyClass.TryParse(s, out var result))
{
  // use result here
}

Da ich den Zweig nur betrete, wenn die Operation erfolgreich ist, sollte das Ergebnis in diesem Zweig niemals null sein. Aber weil ich es als nullbar markiert habe, muss ich jetzt entweder danach suchen oder ! Verwenden, um Folgendes zu überschreiben:

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // compiler warning, could be null
  Console.WriteLine("Look: {0}", result!.SomeProperty); // need override
}

Das ist hässlich und ein bisschen unergonomisch.

Aufgrund des typischen Verwendungsmusters habe ich eine andere Option: Lüge über den Ergebnistyp:

public static bool TryParse(string s, out MyClass result) // not nullable
{
   // Happy path sets result to non-null and returns true.
   // Error path does this:
   result = null!; // override compiler complaint
   return false;
}

Jetzt wird die typische Verwendung schöner:

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // no warning
}

bei atypischer Verwendung wird jedoch nicht die Warnung angezeigt, die es sollte:

else
{
  Console.WriteLine("Fail: {0}", result.SomeProperty);
  // Yes, result is in scope here. No, it will never be non-null.
  // Yes, it will throw. No, the compiler won't warn about it.
}

Jetzt bin ich mir nicht sicher, welchen Weg ich hier gehen soll. Gibt es eine offizielle Empfehlung des C # -Sprachteams? Gibt es bereits CoreFX-Code, der in nicht nullfähige Referenzen konvertiert wurde und mir zeigen könnte, wie das geht? (Ich habe nach TryParse Methoden gesucht. IPAddress ist eine Klasse mit einer, die jedoch nicht in den Hauptzweig von corefx konvertiert wurde.)

Und wie geht generischer Code wie Dictionary.TryGetValue Damit um? (Möglicherweise mit einem speziellen MaybeNull -Attribut von dem, was ich gefunden habe.) Was passiert, wenn ich ein Dictionary mit einem nicht nullbaren Werttyp instanziiere?

24
Sebastian Redl

Das bool/out-var-Muster funktioniert nicht gut mit nullbaren Referenztypen, wie Sie beschreiben. Verwenden Sie die Funktion, um die Dinge zu vereinfachen, anstatt gegen den Compiler zu kämpfen. Wenn Sie die verbesserten Mustervergleichsfunktionen von C # 8 einsetzen, können Sie eine nullfähige Referenz als "Typ des armen Mannes" behandeln:

public static MyClass? TryParse(string s) => …

…

if (TryParse(someString) is {} myClass)
{
    // myClass wasn't null, we are good to use it
}

Auf diese Weise vermeiden Sie das Herumspielen mit out -Parametern und müssen nicht mit dem Compiler kämpfen, um null mit nicht nullbaren Referenzen zu mischen.

Und wie geht generischer Code wie Dictionary.TryGetValue Damit um?

An diesem Punkt fällt dieser "Typ des armen Mannes" herunter. Die Herausforderung besteht darin, dass der Compiler bei Verwendung von nullbaren Referenztypen (NRT) Foo<T> Als nicht nullbar behandelt. Versuchen Sie jedoch, es in Foo<T?> Zu ändern, und es wird gewünscht, dass T, das auf eine Klasse oder Struktur als nullbare Werttypen beschränkt ist, aus Sicht der CLR eine ganz andere Sache ist. Hierfür gibt es eine Vielzahl von Problemumgehungen:

  1. Aktivieren Sie die NRT-Funktion nicht.
  2. Verwenden Sie default (zusammen mit !) Für out -Parameter, obwohl sich Ihr Code ohne Nullen anmeldet.
  3. Verwenden Sie einen echten Maybe<T> - Typ als Rückgabewert, der dann niemals null ist, und verpacken Sie bool und out T In HasValue und Value Eigenschaften oder solche,
  4. Verwenden Sie ein Tupel:
public static (bool success, T result) TryParse<T>(string s) => …
…

if (TryParse<MyClass>(someString) is (true, var result))
{
    // result is valid here, as success is true
}

Ich persönlich bevorzuge die Verwendung von Maybe<T>, Aber es unterstützt eine Dekonstruktion, damit es wie in 4 oben als Tupel musterangepasst werden kann.

11
David Arno

Wenn Sie wie ich etwas spät dazu kommen, hat sich das .NET-Team herausgestellt, dass es über eine Reihe von Parameterattributen wie MaybeNullWhen(returnValue: true) in System.Diagnostics.CodeAnalysis Leerzeichen, das Sie für das Versuchsmuster verwenden können.

Zum Beispiel:

wie geht generischer Code wie Dictionary.TryGetValue damit um?

bool TryGetValue(TKey key, [MaybeNullWhen(returnValue: false)] out TValue value);

was bedeutet, dass Sie angeschrien werden, wenn Sie nicht nach einem true suchen

// This is okay:
if(myDictionary.TryGetValue("cheese", out var result))
{
  var more = result * 42;
}

// But this is not:
_ = myDictionary.TryGetValue("cheese", out var result);
var more = result * 42;
// "CS8602: Dereference of a potentially null reference"

Weitere Details:

18
Nick Darvey

Ich glaube nicht, dass es hier einen Konflikt gibt.

ihr Einwand gegen

public static bool TryParse(string s, out MyClass? result);

ist

Da ich den Zweig nur betrete, wenn die Operation erfolgreich ist, sollte das Ergebnis in diesem Zweig niemals null sein.

Tatsächlich hindert jedoch nichts die Zuweisung von null zum out-Parameter in den TryParse-Funktionen des alten Stils.

z.B.

MyJsonObject.TryParse("null", out obj) //sets obj to a null MyJsonObject and returns true

Die Warnung an den Programmierer, wenn er den Parameter out ohne Überprüfung verwendet, ist korrekt. Sie sollten überprüfen!

Es wird eine Menge Fälle geben, in denen Sie gezwungen sind, nullbare Typen zurückzugeben, bei denen der Hauptzweig des Codes einen nicht nullbaren Typ zurückgibt. Die Warnung dient nur dazu, diese explizit zu machen. dh.

MyClass? x = (new List<MyClass>()).FirstOrDefault(i=>i==1);

Die nicht nullfähige Methode zum Codieren löst eine Ausnahme aus, bei der es eine Null gegeben hätte. Ob Sie analysieren, bekommen oder zuerst

MyClass x = (new List<MyClass>()).First(i=>i==1);
3
Ewan