views:

102

answers:

2

Disclaimer 1: Crazy pedantic language-bending drivel ahead.

Disclaimer 2: To any clients present or future - I am not billing you for this.

Alright, so this is not really necessary but I'm messing around with creating plugins for xunit.net and an interesting scenario comes up

Currently, the example of the SubSpec extension that ships with the source works something like this:

[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));
}

That's not the beautiful, elegant C# that I know and love - I don't like declaring uninitialized variables and I prefer not to declare them at all. I would much prefer to do something like this:

[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));
}

In this case, the Context() extension method would have the signature void Context(this string, Action setUpWith) would have the signature T Context<T>(this string, Func<T> setUpWith). An interesting problem arises in implementation though since the setUpWith delegate isn't actually executed at this time, it is merely stored and then executed later by the custom Specification attribute. This means that there is not really a T to return at this time. However, since the stored delegates are executed in order, I can guarantee that it will exist by the time the delegate in the Do() method is called.

So what I would be nice is to return a dynamic proxy for T, but this is not really feasible since one of the things I'd like to be able to do is use anonymous types which would make T sealed.

Now in C++ land I believe that it would be possible to allocate memory to the object and return the "object" as a reference to that bit of space which will then get filled in eventually. This is essentially what would happen if I passed T in as a ref parameter (but then I'd have to declare it defeating the point).

Still, there are corners of C# that I have not explored. Can anyone think of a way to squeeze my desired syntax out of this?

PS. I have a couple solutions with slight modifications to the syntax which I will post as answers below.

A: 

Ok, so here are my possible solutions that get me close to the syntax that I want

  • I change the method signature to the following:

    dynamic Context(this string msg, Func<dynamic> setUpWith);

Here I would return an implementation of DynamicObject that passes through any requests for properties or methods to whatever is returned when setUpWith executes.

Pros: Pretty close to what I want

Cons: C#4.0 only. Implementing DynamicObject is a hassle. No intellisense support.

  • I change the method signature to the following:

    Lazy<T> Context<T>(this string msg, Func<T> setUpWith);

Pros: Intellisense. Simple to implement.

Cons: Have to call .Value before accessing underlying type which is annoying.

  • I change the other methods signatures to the following:

    void Do(this string msg, Func<dynamic> doThis);

which are invoked with

  "adding 1 + 1".Do(x=>x.Calc.Add(1, 1));
  "will decrease user's quota by one".Assert(x=>x.User.CalculationsLeft.ShouldEqual(99));

Where the value is stored and injected by the specification framework.

Pros: No need to track the variables outside the closure scope at all.

Cons: No intellisense. Have to change two methods instead of one so more code to refactor.

George Mauer
+4  A: 

There is surely a way to design the API in a way such that you don't have to use uninitialized variables and set their values in a lambda (in fact, functional programming can live without mutation quite easily).

I'm not all that familiar with the API you're talking about, but if you modified the API to look roughly like this, then I think it should work nicely without mutation:

"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)); 
   });

By wrapping the body that should be run after the context is initialized into another lambda expression, you should be able to avoid the problem with initialization. The Then method would simply store the lambda function somewhere (and call it with the returned context once the state is initialized).

Tomas Petricek
Hmm, so the instance is stored in the wrapper and passed into that lambda. I like that
George Mauer