Злоупотребление затворами? Нарушения различных принципов? Или хорошо?
Редактировать: исправлены некоторые проблемы с синтаксисом и согласованностью, чтобы сделать код немного более понятным и близким к тому, что я на самом деле делаю.
У меня есть код, который выглядит следующим образом:
SomeClass someClass;
var finalResult =
DoSomething(() =>
{
var result = SomeThingHappensHere();
someClass = result.Data;
return result;
})
.DoSomething(() => return SomeOtherThingHappensHere(someClass))
.DoSomething(() => return AndYetAnotherThing())
.DoSomething(() => return AndOneMoreThing(someClass))
.Result;
HandleTheFinalResultHere(finalResult);
где DoSomething
Метод является методом расширения, и он ожидает, что в него будет передан Func. Таким образом, каждый из вызовов метода в каждом из DoSomething => lambda возвращает тип Result.
это похоже на монаду Может быть. За исключением того, что вместо проверки на нулевые значения, я проверяю состояние класса Result и либо вызываю Func, который был передан в DoSomething, либо возвращаю предыдущий Result без вызова Func
проблема, с которой я сталкиваюсь, заключается в том, что я хочу иметь такую композицию в своем коде, но мне также нужно иметь возможность передавать данные из одного составного результата вызова в вызов другого, как вы можете видеть с помощью someClass
переменная.
Мой вопрос не в том, является ли это технически правильным или нет, я знаю, что это работает, потому что я сейчас это делаю. Мой вопрос заключается в том, является ли это злоупотреблением замыканиями, разделением команд и запросов или какими-либо другими принципами... а затем спросить, какие существуют более эффективные шаблоны для решения этой ситуации, потому что я совершенно уверен, что застрял в режиме "блестящий новый молот" с этим типом кода, прямо сейчас.
3 ответа
Как уже было отмечено, вы почти внедрили монаду здесь.
Ваш код немного не элегантен, потому что у лямбд есть побочные эффекты. Монады решают это более элегантно.
Итак, почему бы не превратить ваш код в настоящую монаду?
Бонус: вы можете использовать синтаксис 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 to Results это сначала выполняется SomeThingHappensHere
, Если это удастся, он получает значение Data
Свойство результата и выполняет SomeOtherThingHappensHere
, Если это успешно, он выполняет AndYetAnotherThing
, и так далее.
Как видите, вы можете легко связывать операции и ссылаться на результаты предыдущих операций. Каждая операция будет выполняться одна за другой, и выполнение будет остановлено при возникновении ошибки.
from x in
немного в каждой строке немного шумно, но IMO ничто из сопоставимой сложности не станет более читабельным, чем это!
Как мы делаем эту работу?
Монады в C# состоят из трех частей:
тип Something-of-T,
Select
/SelectMany
методы расширения для него, иметод для преобразования T в Something-of-T.
Все, что вам нужно сделать, это создать что-то, что будет похоже на монаду, будет похоже на монаду и пахнет монадой, и все будет работать автоматически.
Типы и методы для 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
Похоже, вы создали нечто очень похожее на монаду.
Вы можете сделать это правильной монадой, сделав свой делегат типа Func<SomeClass, SomeClass>
есть способ настроить начальный SomeClass
значение для передачи и иметь DoSomething
передать возвращаемое значение одного в качестве параметра следующего - это сделало бы цепочку явной, а не полагаясь на разделяемое состояние с лексической областью.
Недостатком этого кода является неявная связь между первой и второй лямбдами. Я не уверен в лучшем способе исправить это.