Estoy tratando de crear un tipo similar al de Rust Result
o Haskell Either
y he llegado hasta aquí:
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 ambos parámetros de tipo están restringidos a ser notnull
, ¿por qué se queja (en cualquier lugar donde haya un parámetro de tipo con el ?
signo anulable después) que:
Se debe saber que un parámetro de tipo anulable es un tipo de valor o un tipo de referencia no anulable. Considere agregar una restricción de 'clase', 'estructura' o tipo.
?
Estoy usando C # 8 en .NET Core 3 con tipos de referencia anulables habilitados.
c#
generics
type-constraints
c#-8.0
nullable-reference-types
Zapato diamente
fuente
fuente
Respuestas:
Básicamente, estás pidiendo algo que no se puede representar en IL. Los tipos de valores anulables y los tipos de referencia anulables son bestias muy diferentes, y aunque se parecen en el código fuente, el IL es muy diferente. La versión anulable de un tipo de valor
T
es un tipo diferente (Nullable<T>
) mientras que la versión anulable de un tipo de referenciaT
es el mismo tipo, con atributos que le dicen al compilador qué esperar.Considere este ejemplo más simple:
Eso no es válido por la misma razón.
Si nos limitamos
T
a ser una estructura, la IL generada para elGetNullValue
método tendría un tipo de retorno deNullable<T>
.Si restringimos
T
a ser un tipo de referencia no anulable, entonces la IL generada para elGetNullValue
método tendría un tipo de retorno deT
, pero con un atributo para el aspecto de nulabilidad.El compilador no puede generar IL para un método que tiene un tipo de retorno de ambos
T
yNullable<T>
al mismo tiempo.Esto es básicamente todo el resultado de que los tipos de referencia anulables no son un concepto CLR en absoluto: es solo magia del compilador para ayudarlo a expresar intenciones en el código y hacer que el compilador realice algunas comprobaciones en tiempo de compilación.
Sin embargo, el mensaje de error no es tan claro como podría ser.
T
se sabe que es "un tipo de valor o un tipo de referencia no anulable". Un mensaje de error más preciso (pero significativamente más extenso) sería:En ese punto, el error se aplicaría razonablemente a nuestro código: el parámetro de tipo no "se sabe que es un tipo de valor" y no "se sabe que es un tipo de referencia no anulable". Se sabe que es uno de los dos, pero el compilador necesita saber cuál .
fuente
Nullable<T>
es un tipo especial que no puedes hacer tú mismo. Y luego está el punto extra de cómo se hace el boxeo con tipos anulables.El motivo de la advertencia se explica en la sección
The issue with T?
de Prueba de tipos de referencia anulables . Para resumir, si lo usaT?
, debe especificar si el tipo es una clase o estructura. Puede terminar creando dos tipos para cada caso.El problema más profundo es que usar un tipo para implementar Result y mantener los valores Success y Error devuelve los mismos problemas que se suponía que Result solucionaría, y algunos más.
Resultado (y cualquiera) en F #
El punto de partida debe ser el tipo de resultado de F # y las uniones discriminadas. Después de todo, esto ya funciona en .NET.
Un tipo de resultado en F # es:
Los tipos en sí solo llevan lo que necesitan.
Los DU en F # permiten una concordancia exhaustiva de patrones sin requerir nulos:
Emulando esto en C # 8
Desafortunadamente, C # 8 aún no tiene DU, están programados para C # 9. En C # 8 podemos emular esto, pero perdemos una coincidencia exhaustiva:
Y úsalo:
Sin una exhaustiva coincidencia de patrones, tenemos que agregar esa cláusula predeterminada para evitar advertencias del compilador.
Todavía estoy buscando una manera de obtener una concordancia exhaustiva sin introducir valores muertos, incluso si son solo una Opción.
Opción / Quizás
Crear una clase de Opción por la forma en que usa una concordancia exhaustiva es más simple:
Que se puede usar con:
fuente