8

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

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

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 类型。

这类似于Maybe monad。除了检查空值之外,我正在检查 Result 类的状态,并且要么调用传递给 DoSomething 的 Func,要么在不调用 Func 的情况下返回先前的 Result

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

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

4

3 回答 3

11

如前所述,您几乎在这里实现了 Monad。

您的代码有点不雅,因为 lambdas 有副作用。单子更优雅地解决了这个问题。

那么,为什么不把你的代码变成一个合适的 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 to Results,这首先执行SomeThingHappensHere。如果成功,它将获取Data结果属性的值并执行SomeOtherThingHappensHere。如果成功,则执行AndYetAnotherThing,依此类推。

如您所见,您可以轻松链接操作并参考先前操作的结果。每个操作都会一个接一个地执行,遇到错误就会停止执行。

每行的from x in位有点嘈杂,但 IMO 没有什么比这更易读的了!


我们如何进行这项工作?

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

  • 一个类型的Something-of-T

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

  • 一种将T转换为Something-of-T的方法。

你需要做的就是创造一些看起来像 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;
        }
    }
}

扩展方法:

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> 类和扩展方法,例如,实现更复杂的规则。只有扩展方法的签名必须与所述完全一致。

于 2010-10-05T01:51:33.710 回答
2

在我看来,您在这里构建了与 monad 非常相似的东西。

您可以通过将委托类型设为 a 来使其成为适当的 monad Func<SomeClass, SomeClass>,有某种方法来设置SomeClass要传入的初始值,并将DoSomethingone 的返回值作为下一个的参数传递——这将使链接显式而不是依赖于词法范围的共享状态。

于 2010-10-05T00:15:30.273 回答
0

这段代码的弱点是第一个和第二个 lambda 之间的隐式耦合。我不确定修复它的最佳方法。

于 2010-10-05T00:21:30.747 回答