Estou tentando criar um tipo semelhante ao de Rust Result
ou Haskell Either
e cheguei até aqui:
public struct Result<TResult, TError>
where TResult : notnull
where TError : notnull
{
private readonly OneOf<TResult, TError> Value;
public Result(TResult result) => Value = result;
public Result(TError error) => Value = error;
public static implicit operator Result<TResult, TError>(TResult result)
=> new Result<TResult, TError>(result);
public static implicit operator Result<TResult, TError>(TError error)
=> new Result<TResult, TError>(error);
public void Deconstruct(out TResult? result, out TError? error)
{
result = (Value.IsT0) ? Value.AsT0 : (TResult?)null;
error = (Value.IsT1) ? Value.AsT1 : (TError?)null;
}
}
Dado que os parâmetros dos dois tipos estão restritos notnull
, por que está reclamando (em qualquer lugar onde exista um parâmetro do tipo com o ?
sinal nulo ):
Um parâmetro de tipo anulável deve ser conhecido como um tipo de valor ou tipo de referência não anulável. Considere adicionar uma restrição de 'classe', 'estrutura' ou tipo.
?
Estou usando o C # 8 no .NET Core 3 com os tipos de referência anuláveis ativados.
c#
generics
type-constraints
c#-8.0
nullable-reference-types
Shoe Diamente
fonte
fonte
Respostas:
Basicamente, você está pedindo algo que não pode ser representado em IL. Tipos de valor nulo e tipos de referência nulo são bestas muito diferentes e, embora pareçam semelhantes no código-fonte, a IL é muito diferente. A versão anulável de um tipo de valor
T
é um tipo diferente (Nullable<T>
) enquanto a versão anulável de um tipo de referênciaT
é do mesmo tipo, com atributos informando ao compilador o que esperar.Considere este exemplo mais simples:
Isso é inválido pelo mesmo motivo.
Se restringirmos
T
a uma estrutura, o IL gerado para oGetNullValue
método terá um tipo de retornoNullable<T>
.Se restringirmos
T
a ser um tipo de referência não anulável, a IL gerada para oGetNullValue
método terá um tipo de retorno deT
, mas com um atributo para o aspecto de anulabilidade.O compilador não pode gerar IL para um método que tenha um tipo de retorno de ambos
T
eNullable<T>
ao mesmo tempo.Isso é basicamente o resultado de os tipos de referência anuláveis não serem um conceito CLR - é apenas a mágica do compilador para ajudá-lo a expressar intenções no código e fazer com que o compilador execute algumas verificações no momento da compilação.
A mensagem de erro não é tão clara quanto pode ser.
T
é conhecido por ser "um tipo de valor ou tipo de referência não anulável". Uma mensagem de erro mais precisa (mas significativamente mais detalhada) seria:Nesse ponto, o erro seria razoavelmente aplicado ao nosso código - o parâmetro type não é "conhecido por ser um tipo de valor" e "não é conhecido por ser um tipo de referência não anulável". É conhecido por ser um dos dois, mas o compilador precisa saber qual .
fonte
Nullable<T>
é um tipo especial que você não pode fazer por si mesmo. E há o ponto de bônus de como o boxe é feito com tipos de nulllable.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.
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 # é:
Os próprios tipos carregam apenas o que precisam.
As DUs em F # permitem uma correspondência exaustiva de padrões sem exigir nulos:
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:
E use-o:
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:
Que pode ser usado com:
fonte