[英]Abuse of Closures? Violations of various principles? Or ok?
编辑:修复了几个语法和一致性问题,使代码更加明显,接近我实际做的事情。
我有一些看起来像这样的代码:
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类型。
这类似于一个可能的monad 。 除了检查空值,我检查Result类的状态,并调用传递给DoSomething的Func或返回前一个Result而不调用Func
我面临的问题是希望在我的代码中使用这种组合,但我还需要能够将一个组合调用结果中的数据传递给另一个调用结果,正如您可以看到someClass
变量。
我的问题不是这在技术上是否正确......我知道这是有效的,因为我现在正在这样做。 我的问题是这是否是滥用闭包,命令查询分离或任何其他原则......然后询问有什么更好的模式来处理这种情况,因为我很确定我是现在,这种类型的代码陷入了“闪亮的新锤子”模式。
正如已经指出的那样,你几乎已经在这里实现了Monad。
你的代码有点不优雅,因为lambdas有副作用。 Monads更优雅地解决了这个问题。
那么,为什么不把你的代码变成一个合适的Monad呢?
额外奖励:您可以使用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
bit有点吵,但IMO没有任何相似的复杂性会比这更具可读性!
我们如何使这项工作?
C#中的Monads由三部分组成:
类型Something-of-T ,
Select
/ SelectMany
它扩展方法,和
将T转换为T的Something的方法 。
你需要做的就是创造一个看起来像Monad的东西,感觉像Monad,闻起来像Monad,一切都会自动运行。
LINQ to Results的类型和方法如下。
结果<T>类型:
一个表示结果的简单类。 结果是类型T的值或错误。 结果可以从T或Exception构造。
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>类和扩展方法,以实现更复杂的规则。 只有扩展方法的签名必须完全符合规定。
在我看来,你已经在这里建造了一个非常类似monad的东西。
您可以通过使您的委托类型为Func<SomeClass, SomeClass>
,使某些方法能够设置要传入的初始SomeClass
值,并让DoSomething
将返回值1作为下一个参数传递给它 - - 这将使链接显式而不是依赖于词法范围的共享状态。
这段代码的弱点是第一个和第二个lambda之间的隐式耦合。 我不确定解决它的最佳方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.