免责声明1:疯狂的迂腐语言 - 向前弯曲。
免责声明2:对于现在或未来的任何客户 - 我不会向您收费。
好吧,所以这不是必要,但我正在为xunit.net创建插件而烦恼并且出现了一个有趣的场景
目前,源代码附带的SubSpec扩展示例的工作原理如下:
[Specification]
void calculator_addition() {
Calculator calc = null;
User user = null;
"Given a calculator and a user".Context(()=> {
calc = new Calculator();
user = new User(calculationQuota: 100);
});
"adding 1 + 1".Do(()=>result = calc.Add(1, 1));
"will equal 2".Assert(()=>calc.Result.ShouldEqual(2));
"will decrease user's quota by one".Assert(()=>user.CalculationsLeft.ShouldEqual(99));
}
这不是我所熟悉和喜爱的美丽,优雅的C# - 我不喜欢声明未初始化的变量,我宁愿不要声明它们。我更喜欢做这样的事情:
[Specification]
void calculator_addition() {
var _ =
"Given a calculator and a user".Context(()=> new {
Calc = new Calculator(),
User = new User(calculationQuota: 100),
});
"adding 1 + 1".Do(()=>_.Calc.Add(1, 1));
"will equal 2".Assert(()=>_.Calc.Result.ShouldEqual(2));
"will decrease user's quota by one".Assert(()=>_.User.CalculationsLeft.ShouldEqual(99));
}
在这种情况下,Context()扩展方法将使签名void Context(this string, Action setUpWith)
具有签名T Context<T>(this string, Func<T> setUpWith)
。实现中出现了一个有趣的问题,因为此时setUpWith委托实际上并未执行,它只是存储然后由自定义Specification属性执行。这意味着此时没有真正返回的T.但是,由于存储的委托按顺序执行,我可以保证在调用Do()方法中的委托时它将存在。
所以我很高兴为T返回一个动态代理,但这不是真的可行,因为我希望能够做的事情之一是使用匿名类型,这将使T密封。
现在在C ++领域,我相信可以为对象分配内存并返回“对象”作为对该位空间的引用,然后最终将被填充。如果我将T in作为ref参数传递(但之后我必须声明它击败了这一点),这基本上会发生什么。
尽管如此,我还没有探索过C#的角落。任何人都可以想到一种方法来挤出我想要的语法吗?
PS。我有几个解决方案,对语法略有修改,我将在下面给出答案。
答案 0 :(得分:4)
肯定有一种方法可以设计API,使您不必使用未初始化的变量并在lambda中设置它们的值(实际上,函数式编程可以很容易地在没有变异的情况下生存)。
我对你所谈论的API并不熟悉,但如果你修改API看起来大致如此,那么我认为它应该可以正常工作而不会发生变异:
"Given a calculator and a user"
// Constructs and returns anonymous object that will hold the state
// ('Context' takes 'Func<T>' and returns some wrapper)
.Context(() => new { Calc = new Calculator();
User = new User(calculationQuota: 100) })
.Then(ctx => {
"adding 1 + 1".Do(() => result = ctx.Calc.Add(1, 1));
"will equal 2".Assert(() => ctx.Calc.Result.ShouldEqual(2));
"will decrease user's quota by one".Assert(() =>
ctx.User.CalculationsLeft.ShouldEqual(99));
});
通过将上下文初始化后应该运行的主体包装到另一个lambda表达式中,您应该能够避免初始化问题。 Then
方法只是将lambda函数存储在某处(并在初始化状态后使用返回的上下文调用它)。
答案 1 :(得分:0)
好的,所以这里是我可能的解决方案,让我接近我想要的语法
我将方法签名更改为以下内容:
dynamic Context(this string msg, Func<dynamic> setUpWith);
在这里,我将返回一个DynamicObject实现,该实现将对属性或方法的任何请求传递给setUpWith执行时返回的任何内容。
优点:非常接近我想要的
缺点:仅限C#4.0。实现DynamicObject是一件麻烦事。没有intellisense支持。
我将方法签名更改为以下内容:
Lazy<T> Context<T>(this string msg, Func<T> setUpWith);
优点:智能感知。易于实施。
缺点:在访问令人讨厌的基础类型之前,必须调用.Value。
我将其他方法签名更改为以下内容:
void Do(this string msg, Func<dynamic> doThis);
用
调用 "adding 1 + 1".Do(x=>x.Calc.Add(1, 1));
"will decrease user's quota by one".Assert(x=>x.User.CalculationsLeft.ShouldEqual(99));
值由规范框架存储和注入。
优点:根本不需要跟踪闭包范围之外的变量。
缺点:没有intellisense。必须更改两个方法而不是一个更多的代码来重构。