Обновление для структур
Код не изменяется при изменении типов результатов на структуры.Чтобы использовать параметры типа структуры, необходимо добавить следующие ограничения в интерфейс и типы:
where TResult : struct
where TError : struct
Когда я думаю о шаблоне Either, я думаю о F #, сопоставлении с образцом и различимых объединениях, не нули.Фактически, Either
- это способ избежать нулей.На самом деле код вопроса выглядит как попытка создать Result type , а не просто Either. Скотт Влашин * Железнодорожно-ориентированное программирование показывает, как такой тип может использоваться для реализации обработки ошибок на функциональном языке.
В F # тип Result определяется как:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
Мы еще не можем сделать это в C # 8, потому что нет дискриминационных союзов.Они запланированы для C # 9.
Сопоставление с образцом
Что мы можем сделать, это использовать сопоставление с образцом, чтобы получить то же поведение, например:
interface IResult<TResult,TError>{} //No need for an actual implementation
public class Success<TResult,TError>:IResult<TResult,TError>
{
public TResult Result {get;}
public Success(TResult result) { Result=result;}
}
public class Error<TResult,TError>:IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error) { ErrorValue=error;}
}
Таким образом, нет способа создать IResult<>
, который будет и успешным, и ошибочным.Это можно использовать с сопоставлением с образцом, например:
IResult<int,string> someResult=.....;
if(someResult is Success<int,string> s)
{
//Use s.Result here
}
Упрощение выражений
Учитывая паттерны свойств C # 8 , это можно переписатькак:
if(someResult is Success<int,string> {Result: var result} )
{
Console.WriteLine(result);
}
или, используя выражения-переключатели, типичный железнодорожный вызов:
IResult<int,string> DoubleIt(IResult<int,string> data)
{
return data switch { Error<int,string> e=>e,
Success<int,string> {Result: var result}=>
new Success<int,string>(result*2),
_ => throw new Exception("Unexpected type!")
};
}
F # не понадобится throw
, поскольку * 1051 не существует* будет чем-то отличным от Ok
или Error
.В C # у нас пока нет этой функции .
Выражение-переключатель допускает исчерпывающее сопоставление.Я думаю, что компилятор выдаст предупреждение, если предложение по умолчанию также отсутствует.
С деконструкторами
Выражения могут быть упрощены немного больше, если типы имеют деконструкторы,Например:
public class Success<TResult,TError>:IResult<TResult,TError>
{
public TResult Result {get;}
public Success(TResult result) { Result=result;}
public void Deconstruct(out TResult result) { result=Result;}
}
public class Error<TResult,TError>:IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error) { ErrorValue=error;}
public void Deconstruct(out TError error) { error=ErrorValue;}
}
В этом случае выражение может быть записано как:
return data switch {
Error<int,string> e => e,
Success<int,string> (var result) => new Success<int,string>(result*3),
_ => throw new Exception("Unexpected type!")
};
Обнуляемость
Вопрос начался с обнуляемых ссылочных типовтак что насчет обнуляемости?Получим ли мы предупреждение в C # 8, если попытаемся передать значение nulll?
Да, если включены NRT.Этот код:
#nullable enable
void Main()
{
IResult<string,string> data=new Success<string,string>(null);
var it=Append1(data);
Console.WriteLine(it);
}
IResult<string,string> Append1(IResult<string,string> data)
{
return data switch { Error<string,string> e=>e,
Success<string,string> (var result)=>
new Success<string,string>(result+"1"),
_ => throw new Exception("Unexpected type!")
};
}
Генерирует CS8625: Cannot convert null literal to non-nullable reference type
Пытается
string? s=null;
IResult<string,string> data=new Success<string,string>(s);
Генерирует CS8604: Possible null reference argument ....