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

StackOverflow https://stackoverflow.com/questions/3860051

Вопрос

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

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

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 => возвращает тип результата.

это похоже на Может быть, монада.За исключением того, что вместо проверки нулей я проверяю состояние класса Result и либо вызываю Func, который был передан в DoSomething, либо возвращаю предыдущий результат без вызова Func.

проблема, с которой я сталкиваюсь, заключается в том, что я хочу иметь такую ​​​​композицию в своем коде, но мне также нужно иметь возможность передавать данные из одного из результатов составленного вызова в вызов другого, как вы можете видеть с помощью someClass переменная.

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

Это было полезно?

Решение

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

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

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

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


Как нам это сделать?

Монады в C# состоят из трех частей:

  • тип Что-то вроде Т,

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

  • метод преобразования Т в Что-то вроде Т.

Все, что вам нужно сделать, это создать что-то, что выглядит как Монада, ощущается как Монада и пахнет Монадой, и все будет работать автоматически.


Виды и способы получения LINQ для результатов следующие.

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

Другие советы

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top