O motivo do aviso é explicado na seção The issue with T?
de experimentar tipos anuláveis referência . Para encurtar a história, se você usar, T?
precisará especificar se o tipo é uma classe ou estrutura. Você pode criar dois tipos para cada caso.
O problema mais profundo é que o uso de um tipo para implementar o Result e reter os valores de Sucesso e Erro traz de volta os mesmos problemas que o resultado deveria corrigir e mais alguns.
- O mesmo tipo precisa carregar um valor morto, o tipo ou o erro, ou trazer de volta nulos
- A correspondência de padrões no tipo não é possível. Você precisaria usar expressões sofisticadas de correspondência de padrões posicionais para que isso funcionasse.
- Para evitar nulos, você terá que usar algo como Option / Maybe, semelhante às Opções do F # . Você ainda carregaria um None por aí, seja pelo valor ou pelo erro.
Resultado (e qualquer um) em F #
O ponto de partida deve ser o tipo de resultado do F # e as uniões discriminadas. Afinal, isso já funciona no .NET.
Um tipo de resultado em F # é:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
Os próprios tipos carregam apenas o que precisam.
As DUs em F # permitem uma correspondência exaustiva de padrões sem exigir nulos:
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
Emulando isso em C # 8
Infelizmente, o C # 8 ainda não tem DUs, eles estão agendados para o C # 9. No C # 8 podemos imitar isso, mas perdemos uma correspondência exaustiva:
#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 use-o:
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()
};
}
Sem uma correspondência exaustiva de padrões, precisamos adicionar essa cláusula padrão para evitar avisos do compilador.
Ainda estou procurando uma maneira de obter uma correspondência exaustiva sem introduzir valores mortos, mesmo que sejam apenas uma opção.
Opção / Talvez
Criar uma classe Option pela maneira que usa uma correspondência exaustiva é mais simples:
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;
}
Que pode ser usado com:
string cateGory = someValue switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name
};