C# 滥用关闭?违反各种原则?还是好?
编辑:修复了几个语法和一致性问题,使代码更清晰,更接近我实际正在做的事情 我有一些代码如下所示:C# 滥用关闭?违反各种原则?还是好?,c#,closures,anti-patterns,principles,command-query-separation,C#,Closures,Anti Patterns,Principles,Command Query Separation,编辑:修复了几个语法和一致性问题,使代码更清晰,更接近我实际正在做的事情 我有一些代码如下所示: SomeClass someClass; var finalResult = DoSomething(() => { var result = SomeThingHappensHere(); someClass = result.Data; return result; }) .DoSomething(() => return SomeOthe
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中的每个方法调用都返回一个结果类型
这类似于一个。除了检查null之外,我正在检查Result类的状态,或者调用传递到DoSomething中的Func,或者在不调用Func的情况下返回上一个结果
我面临的问题是,我希望在代码中有这种组合,但我还需要能够将数据从一个组合的调用结果传递到另一个的调用,正如您通过someClass
变量所看到的那样
我的问题不是这在技术上是否正确。。。我知道这很有效,因为我现在正在做。我的问题是这是否是对闭包、命令查询分离或任何其他原则的滥用。。。然后问有什么更好的模式可以处理这种情况,因为我相当肯定,我现在被困在这种类型的代码的“闪亮的新锤子”模式中。在我看来,你在这里构建了非常类似于单子的东西
您可以通过使您的委托键入a
Func
使其成为适当的monad,并通过某种方式设置要传入的初始SomeClass
值,并让DoSomething将其中一个的返回值作为下一个的参数传递——这将使链接显式化,而不是依赖于词汇范围的共享状态。此代码的弱点是第一个和第二个lambda之间的隐式耦合。我不确定最好的解决办法 如前所述,这里几乎实现了一个Monad
你的代码有点不雅观,因为lambda有副作用。单子更优雅地解决了这个问题
那么,为什么不把你的代码转换成一个合适的单子呢?
奖励:您可以使用LINQ语法
我提出: LINQ对结果的影响
例如: 使用LINQ to Results,首先执行
此处发生的某些事情
。如果成功,它将获取结果的Data
属性的值,并执行SomeOtherThingHappensHere
。如果成功,它将执行和yetanotherthing
,依此类推
如您所见,您可以轻松地链接操作并参考以前操作的结果。每个操作将一个接一个地执行,当遇到错误时,执行将停止
每行x中的位都有点嘈杂,但在我看来,没有比这更复杂的了
我们如何使这项工作顺利进行?
C#中的单子由三部分组成:
- 一种T型的东西
选择
/为它选择许多扩展方法,然后
- 一种将T转换为T中某物的方法
你所需要做的就是创造一些看起来像单子,感觉像单子,闻起来像单子的东西,一切都会自动工作
LINQ to结果的类型和方法如下所示
结果类型:
表示结果的简单类。结果要么是类型为T的值,要么是错误。可以从T或异常构造结果
例如,您可以自由修改结果类和扩展方法,以实现更复杂的规则。只有扩展方法的签名必须完全符合规定。这不太容易阅读……100%同意。可悲的是,与我开始时相比,这仍然是一个显著的进步。再加一点格式可能会有帮助。。。可能…所以本质上你是在尝试进行一系列独立的方法调用,并将每个调用的结果组合成一个结果。对吗?或者你到底想达到什么目的?dtb:这类似于单子。除了检查null之外,我正在检查Result类的状态,或者调用传递到DoSomething中的Func,或者在不调用funcy的情况下返回上一个结果。对于单行lambda,您不需要return关键字。我只是说,是的,我最近一直在学习单子,我试图按照这些思路创造一些东西。您的建议的问题是,我需要始终从函数返回“result”类,在一种情况下,我还需要将“someClass”数据从一个函数返回到下一个函数。+1副作用很难推理和测试。如果您重构为Func
,而不是Action
,并显式地链接操作,那么您会感谢您自己。为什么让结果类型如此重要?它是通过其他人的代码传递的吗?在返回之前,您不能修改它,以便在合成链中附加另一个链接?DoSomething方法中有几个检查和逻辑片段,使用结果类型来确定是否调用传入的Func以获得下一个结果,或者只返回当前的result.Waitaminute。如果Result有一个SomeClass类型的成员(我猜它是某种条件参数块),为什么不使用该成员将其传递到lambda链,而不是创建一个对所有lambda都可见的单独可变变量呢?当然。这是糟糕的设计和语义耦合——你必须知道引擎盖下发生了什么,才能理解将价值从其中一个拉到另一个的后果。太棒了。有一段时间我一直在迷惑蒙娜斯,这很有道理,肯定有助于我的理解。您不能将Select/SelectMany扩展中的大部分逻辑移到Bind(Func)或si中吗
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);
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;
}
}
}
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));
}
}