Как уже отмечалось, вы почти реализовали монаду здесь.
Ваш код немного не элегантен, поскольку у лямбд есть побочные эффекты.Монады решают эту проблему более элегантно.
Итак, почему бы не превратить ваш код в правильную монаду?
Бонус: вы можете использовать синтаксис LINQ!
Я представляю:
LINQ к результатам
Пример:
var result =
from a in SomeThingHappensHere()
let someData = a.Data
from b in SomeOtherThingHappensHere(someData)
from c in AndYetAnotherThing()
from d in AndOneMoreThing(someData)
select d;
HandleTheFinalResultHere(result.Value);
С LINQ к результатам , это сначала выполняет SomeThingHappensHere
.Если это удается, он получает значение свойства Data
результата и выполняет SomeOtherThingHappensHere
.Если это удается, он выполняет AndYetAnotherThing
и т. Д.
Как видите, вы можете легко объединять операции в цепочку и ссылаться на результаты предыдущих операций.Каждая операция будет выполняться одна за другой, и выполнение будет остановлено при возникновении ошибки.
Бит from x in
в каждой строке немного зашумлен, но IMO ничто из сопоставимой сложности не станет более читабельным, чем эта!
Как мы выполняем эту работу?
Монады в C # состоят из трех частей:
Все, что вам нужно сделать, это создать нечто, похожее на монаду, ощущается как монада и пахнет как монада, и все будет работать автоматически.
Типы и методы LINQ to Results следующие.
Результат тип:
Простой класс, представляющий результат.Результатом является либо значение типа T , либо ошибка.Результат может быть построен из T или из Исключения .
class Result<T>
{
private readonly Exception error;
private readonly T value;
public Result(Exception error)
{
if (error == null) throw new ArgumentNullException("error");
this.error = error;
}
public Result(T value) { this.value = value; }
public Exception Error
{
get { return this.error; }
}
public bool IsError
{
get { return this.error != null; }
}
public T Value
{
get
{
if (this.error != null) throw this.error;
return this.value;
}
}
}
Методы расширения:
Реализациидля методов Select
и SelectMany
.Сигнатуры методов приведены в спецификации C #, поэтому вам нужно беспокоиться только об их реализациях.Это вполне естественно, если вы попытаетесь осмысленно объединить все аргументы метода.
static class ResultExtensions
{
public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return new Result<TResult>(selector(source.Value));
}
public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return selector(source.Value);
}
public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
{
if (source.IsError) return new Result<TResult>(source.Error);
var intermediate = intermediateSelector(source.Value);
if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
}
}
Вы можете свободно изменять класс Result и методы расширения, например, для реализации более сложных правил.Только сигнатуры методов расширения должны быть точно такими, как указано.