La ragione per l'avvertimento è spiegato nella sezione The issue with T?
di provare tipi nullable di riferimento . Per farla breve, se si utilizza T?
è necessario specificare se il tipo è una classe o struttura. Potresti finire per creare due tipi per ogni caso.
Il problema più profondo è che l'uso di un tipo per implementare il risultato e mantenere entrambi i valori di successo ed errore riporta gli stessi problemi che il risultato avrebbe dovuto risolvere e alcuni altri.
- Lo stesso tipo deve contenere un valore morto, o il tipo o l'errore, o riportare valori null
- La corrispondenza del modello sul tipo non è possibile. Dovresti usare alcune fantasiose espressioni di corrispondenza del modello posizionale per farlo funzionare.
- Per evitare null dovrai usare qualcosa come Opzione / Forse, simile alle Opzioni di F # . Porteresti comunque un None in giro, sia per il valore che per l'errore.
Risultato (ed entrambi) in F #
Il punto di partenza dovrebbe essere il tipo di risultato di F # e i sindacati discriminati. Dopotutto, questo funziona già su .NET.
Un tipo di risultato in F # è:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
I tipi stessi portano solo ciò di cui hanno bisogno.
I DU in F # consentono una corrispondenza esaustiva del modello senza richiedere null:
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
Emulazione di questo in C # 8
Sfortunatamente, C # 8 non ha ancora DU, sono programmati per C # 9. In C # 8 possiamo emularlo, ma perdiamo una corrispondenza esaustiva:
#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;
}
E usalo:
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()
};
}
Senza una corrispondenza esaustiva del modello, dobbiamo aggiungere quella clausola predefinita per evitare avvisi del compilatore.
Sto ancora cercando un modo per ottenere una corrispondenza esaustiva senza introdurre valori morti, anche se sono solo un'opzione.
Opzione / Forse
La creazione di una classe Option mediante l'utilizzo di una corrispondenza esaustiva è più semplice:
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;
}
Quale può essere usato con:
string cateGory = someValue switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name
};