滥用关闭?违反各种原则?还是好吗?

Der*_*ley 8 c# closures anti-patterns principles command-query-separation

编辑:修复了几个语法和一致性问题,使代码更加明显,接近我实际做的事情.

我有一些看起来像这样的代码:

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);
Run Code Online (Sandbox Code Playgroud)

其中DoSomething方法是扩展方法,并且它期望传递给它的Func.因此,每个DoSomething => lambda中的每个方法调用都返回一个Result类型.

这类似于一个可能的monad.除了检查空值,我检查Result类的状态,并调用传递给DoSomething的Func或返回前一个Result而不调用Func

我面临的问题是希望在我的代码中使用这种组合,但我还需要能够将一个组合调用结果中的数据传递给另一个调用结果,正如您可以看到的那样someClass.

我的问题不是这在技术上是否正确......我知道这是有效的,因为我现在正在这样做.我的问题是这是否是滥用闭包,命令查询分离或任何其他原则......然后询问有什么更好的模式来处理这种情况,因为我很确定我是现在,这种类型的代码陷入了"闪亮的新锤子"模式.

dtb*_*dtb 11

正如已经指出的那样,你几乎已经在这里实现了Monad.

你的代码有点不优雅,因为lambdas有副作用.Monads更优雅地解决了这个问题.

那么,为什么不把你的代码变成一个合适的Monad呢?

额外奖励:您可以使用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);
Run Code Online (Sandbox Code Playgroud)

使用LINQ to Results,首先执行SomeThingHappensHere.如果成功,它将获取Data结果的属性值并执行SomeOtherThingHappensHere.如果成功,则执行AndYetAnotherThing,依此类推.

如您所见,您可以轻松地链接操作并参考先前操作的结果.每个操作将一个接一个地执行,并且在遇到错误时执行将停止.

from x in位每行是有点吵,但IMO没什么可比的复杂性将得到比这更具可读性!


我们如何使这项工作?

C#中的Monads由三部分组成:

  • 类型Something-of-T,

  • Select/ SelectMany扩展方法,以及

  • T转换为TSomething的方法.

你需要做的就是创造一个看起来像Monad的东西,感觉像Monad,闻起来像Monad,一切都会自动运行.


LINQ to Results的类型和方法如下.

结果<T>类型:

一个表示结果的简单类.结果是类型T的值或错误.结果可以从TException构造.

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

扩展方法:

实现了SelectSelectMany方法.方法签名在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));
    }
}
Run Code Online (Sandbox Code Playgroud)

例如,您可以自由修改Result <T>类和扩展方法,以实现更复杂的规则.只有扩展方法的签名必须完全符合规定.