Злоупотребление затворами?Нарушения различных принципов?Или хорошо? - PullRequest
8 голосов
/ 05 октября 2010

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

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

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.

Myвопрос не в том, технически это правильно или нет ... я знаю, что это работает, потому что я сейчас это делаю.Мой вопрос заключается в том, является ли это злоупотреблением замыканиями, разделением команд и запросов или какими-либо другими принципами ... а затем спросить, какие существуют более эффективные шаблоны для решения этой ситуации, потому что я совершенно уверен, чтозастрял в режиме «блестящий новый молот» с этим типом кода, прямо сейчас.

Ответы [ 3 ]

11 голосов
/ 05 октября 2010

Как уже отмечалось, вы почти реализовали монаду здесь.

Ваш код немного не элегантен, поскольку у лямбд есть побочные эффекты.Монады решают эту проблему более элегантно.

Итак, почему бы не превратить ваш код в правильную монаду?

Бонус: вы можете использовать синтаксис 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 # состоят из трех частей:

  • тип Something-of-T ,

  • Select / SelectMany методы расширения для него и

  • aметод преобразования 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 и методы расширения, например, для реализации более сложных правил.Только сигнатуры методов расширения должны быть точно такими, как указано.

2 голосов
/ 05 октября 2010

Похоже, вы создали нечто очень похожее на монаду.

Вы могли бы сделать ее правильной монадой, сделав тип вашего делегата Func<SomeClass, SomeClass>, иметь некоторый способ установить начальное значение SomeClass для передачи и заставить DoSomething передать возвращаемое значение единицы в качестве параметра. следующего - это сделало бы цепочку явной, а не полагаясь на разделяемое состояние с лексической областью.

0 голосов
/ 05 октября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...