【发布时间】:2011-07-21 14:59:56
【问题描述】:
我有一个用流利语法编写的软件。方法链有一个明确的“结束”,在此之前,代码中实际上并没有做任何有用的事情(想想 NBuilder,或者 Linq-to-SQL 的查询生成实际上并没有命中数据库,直到我们使用 ToList() 迭代我们的对象之前) )。
我遇到的问题是其他开发人员对代码的正确使用感到困惑。他们忽略了调用“结束”方法(因此从未真正“做任何事情”)!
我有兴趣强制使用我的一些方法的返回值,这样我们就永远不能“结束链”而不调用“Finalize()”或“Save()”实际工作的方法。
考虑以下代码:
//The "factory" class the user will be dealing with
public class FluentClass
{
//The entry point for this software
public IntermediateClass<T> Init<T>()
{
return new IntermediateClass<T>();
}
}
//The class that actually does the work
public class IntermediateClass<T>
{
private List<T> _values;
//The user cannot call this constructor
internal IntermediateClass<T>()
{
_values = new List<T>();
}
//Once generated, they can call "setup" methods such as this
public IntermediateClass<T> With(T value)
{
var instance = new IntermediateClass<T>() { _values = _values };
instance._values.Add(value);
return instance;
}
//Picture "lazy loading" - you have to call this method to
//actually do anything worthwhile
public void Save()
{
var itemCount = _values.Count();
. . . //save to database, write a log, do some real work
}
}
如您所见,这段代码的正确用法是这样的:
new FluentClass().Init<int>().With(-1).With(300).With(42).Save();
问题在于人们是这样使用它的(认为它与上面的实现相同):
new FluentClass().Init<int>().With(-1).With(300).With(42);
这个问题如此普遍,以至于另一位开发人员出于完全好的意图实际上更改了“Init”方法的名称,以表明该方法正在执行软件的“实际工作”。
像这样的逻辑错误很难发现,当然,它也可以编译,因为调用带有返回值的方法并只是“假装”它返回 void 是完全可以接受的。 Visual Studio 不在乎你是否这样做;您的软件仍将编译和运行(尽管在某些情况下我相信它会引发警告)。当然,这是一个很棒的功能。想象一个简单的“InsertToDatabase”方法,它将新行的 ID 作为整数返回 - 很容易看出在某些情况下我们需要该 ID,而在某些情况下我们可以不用它。
就这个软件而言,绝对没有任何理由避开方法链末端的“保存”功能。这是一个非常专业的实用程序,唯一的好处来自最后一步。
如果他们调用“With()”而不是“Save()”,我希望某人的软件在编译器级别失败。
这似乎是传统方式不可能完成的任务 - 但这就是我来找你们的原因。是否有一个属性可以用来防止方法被“强制转换为无效”或类似的?
注意:已经向我建议的实现此目标的替代方法是编写一套单元测试来强制执行此规则,并使用http://www.testdriven.net 之类的东西将它们绑定到编译器。这是一个可以接受的解决方案,但我希望有更优雅的解决方案。
【问题讨论】:
-
完全在黑暗中拍摄,我不知道这是可能的,但也许 .NET 代码合同?我认为它们已内置于 .NET 4.0
-
代码契约没有需要检查返回值的属性或断言。
-
如果出现此类问题,我会认为此 API 不可用。为什么在这里使用流畅的界面模式?
-
流畅的界面是由客户端的请求。 Tejs 和 Vercas 等解决方案在技术上是万无一失的,但涉及语法更改;最终我可以玩这些,然后看看客户端是否可以处理 API 更改,但是由于该软件已经在使用中,如果能够在不更改任何公共方法签名的情况下应用修补程序会很好。跨度>
标签: c# compiler-construction attributes lazy-loading return-value