Злоупотребление затворами? Нарушения различных принципов? Или хорошо?

Редактировать: исправлены некоторые проблемы с синтаксисом и согласованностью, чтобы сделать код немного более понятным и близким к тому, что я на самом деле делаю.

У меня есть код, который выглядит следующим образом:

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 передать возвращаемое значение одного в качестве параметра следующего - это сделало бы цепочку явной, а не полагаясь на разделяемое состояние с лексической областью.

Недостатком этого кода является неявная связь между первой и второй лямбдами. Я не уверен в лучшем способе исправить это.

Другие вопросы по тегам