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

我有一些代码,如下所示:

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 method 是一个扩展方法,它需要传入一个 Func 。因此,每个 DoSomething => lambda 中的每个方法调用都会返回一个 Result 类型。

这类似于 也许单子. 。除了不检查空值之外,我检查 Result 类的状态,并且调用传递到 DoSomething 的 Func 或返回先前的 Result 而不调用 Func

我面临的问题是希望在我的代码中具有这种组合,但我还需要能够将数据从一个组合调用结果传递到另一个调用结果中,正如您可以在 someClass 多变的。

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

有帮助吗?

解决方案

正如已经指出的,您几乎已经在这里实现了 Monad。

您的代码有点不优雅,因为 lambda 具有副作用。Monad 可以更优雅地解决这个问题。

那么,为什么不把你的代码变成一个合适的 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);

结果的 LINQ, ,这首先执行 SomeThingHappensHere. 。如果成功,它将获得以下值 Data 结果的属性并执行 SomeOtherThingHappensHere. 。如果成功则执行 AndYetAnotherThing, , 等等。

正如您所看到的,您可以轻松地链接操作并引用先前操作的结果。每一项操作都会依次执行,遇到错误就会停止执行。

from x in 每行有点嘈杂,但在我看来,没有任何类似复杂性的东西比这更具可读性!


我们如何让这项工作发挥作用?

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

  • 一种 T 的东西,

  • Select/SelectMany 它的扩展方法,以及

  • 一种转换方法 时间 变成一个 T 的东西.

您所需要做的就是创建一些看起来像 Monad、感觉像 Monad、闻起来像 Monad 的东西,一切都会自动运行。


的类型和方法 结果的 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;
        }
    }
}

扩展方法:

实施方案 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));
    }
}

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

其他提示

在我看来就像你已经建立了非常相似的一个单子的东西在这里。

您可以通过使您的委托类型一Func<SomeClass, SomeClass>使它成为一个适当的单子,有一些方法来设置初始SomeClass值传递的,并有DoSomething传递一个返回值作为下一个的参数 - 这会做出明确的链接而不是依赖于词法作用域共享状态。

此码的缺点是在第一和第二lambda表达式之间的隐式耦合。我不知道的解决它的最好办法。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top