有什么方法可以返回一个尚不存在的对象,但稍后会从C#方法返回?

时间:2010-08-27 23:33:02

标签: c# generics lambda

免责声明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。我有几个解决方案,对语法略有修改,我将在下面给出答案。

2 个答案:

答案 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。必须更改两个方法而不是一个更多的代码来重构。