Der Grund für die Warnung wird im Abschnitt The issue with T?
von Try out Nullable Referenztypen . Lange Rede kurzer Sinn, wenn Sie verwendenT?
, müssen Sie angeben, ob der Typ eine Klasse oder eine Struktur ist. Möglicherweise erstellen Sie für jeden Fall zwei Typen.
Das tiefere Problem besteht darin, dass die Verwendung eines Typs zum Implementieren des Ergebnisses und zum Halten der Werte für Erfolg und Fehler dieselben Probleme zurückbringt, die das Ergebnis beheben sollte, und einige weitere.
- Der gleiche Typ muss einen toten Wert haben, entweder den Typ oder den Fehler, oder Nullen zurückbringen
- Ein Mustervergleich für den Typ ist nicht möglich. Sie müssten einige ausgefallene Ausdrücke für den Positionsmusterabgleich verwenden, damit dies funktioniert.
- Um zu vermeiden , nulls Sie so etwas wie eine Option verwenden / Vielleicht, ähnlich wie F # 's Optionen . Sie würden jedoch immer noch eine Keine mit sich herumtragen, entweder für den Wert oder für den Fehler.
Ergebnis (und entweder) in F #
Der Ausgangspunkt sollte der Ergebnistyp von F # und diskriminierte Gewerkschaften sein. Immerhin funktioniert das schon in .NET.
Ein Ergebnistyp in F # ist:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
Die Typen selbst tragen nur das, was sie brauchen.
DUs in F # ermöglichen einen umfassenden Mustervergleich, ohne dass Nullen erforderlich sind:
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
Emulieren Sie dies in C # 8
Leider hat C # 8 noch keine DUs, sie sind für C # 9 geplant. In C # 8 können wir dies emulieren, aber wir verlieren die erschöpfende Übereinstimmung:
#nullable enable
public interface IResult<TResult,TError>{}
struct Success<TResult,TError> : IResult<TResult,TError>
{
public TResult Value {get;}
public Success(TResult value)=>Value=value;
public void Deconstruct(out TResult value)=>value=Value;
}
struct Error<TResult,TError> : IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error)=>ErrorValue=error;
public void Deconstruct(out TError error)=>error=ErrorValue;
}
Und benutze es:
IResult<double,string> Sqrt(IResult<double,string> input)
{
return input switch {
Error<double,string> e => e,
Success<double,string> (var v) when v<0 => new Error<double,string>("Negative"),
Success<double,string> (var v) => new Success<double,string>(Math.Sqrt(v)),
_ => throw new ArgumentException()
};
}
Ohne erschöpfenden Mustervergleich müssen wir diese Standardklausel hinzufügen, um Compiler-Warnungen zu vermeiden.
Ich bin immer noch auf der Suche nach einer Möglichkeit, eine vollständige Übereinstimmung zu erzielen, ohne tote Werte einzuführen, auch wenn diese nur eine Option sind.
Option / Vielleicht
Das Erstellen einer Optionsklasse mithilfe eines umfassenden Abgleichs ist einfacher:
readonly struct Option<T>
{
public readonly T Value {get;}
public readonly bool IsSome {get;}
public readonly bool IsNone =>!IsSome;
public Option(T value)=>(Value,IsSome)=(value,true);
public void Deconstruct(out T value,out bool isSome)=>(value,isSome)=(Value,IsSome);
}
//Convenience methods, similar to F#'s Option module
static class Option
{
public static Option<T> Some<T>(T value)=>new Option<T>(value);
public static Option<T> None<T>()=>default;
}
Welches kann verwendet werden mit:
string cateGory = someValue switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name
};