Fabian's Mix

Mixins, .NET, and more

May the Params be With you

with 2 comments

This is the fifth in a series posts. The previous items are:

Last time, I announced that I’d probably write about mixins and inheritance next. That’s still on my list, but today, I would like to talk about two lines of code I’ve posted in the past and never really explained. Here they are:

ObjectFactory.Create<MyTargetClass> ().With (x, y, z);

ObjectFactory.Create<File> (ParamList.Empty);

The first line is from the post “What can we do for you? (Features of re-motion mixins)”, and in that post, I wrote:

The With is a wart, unfortunately. Short story: x, y, and z are the constructor parameters for the mixed object, and the With method takes care of their types being respected. (If you are thinking, “Why aren’t they just using a params object[]?” – because that wouldn’t respect the parameters’ types. I’ll explain another time.)

With will soon be replaced by a ParameterList object or similar, so that it will read somewhat like this:

MyTargetClass myMixedObject =
 
ObjectFactory.Create<MyTargetClass> (ParameterList.Create(x, y, z));

So, With was a wart, and it has been replaced. Not by a ParameterList object, but by a ParamList object. But why had it been there in the first place?

To answer this, let’s take a look at the interestingly amusing topic of dynamically instantiating classes. With parameters.

Suppose you are implementing a framework, for example one providing mixin support for statically typed .NET languages. To do so, you will probably need to provide a generic factory, ie. a class that allows dynamic instantiation of arbitrary objects. Let’s call it TheObjectFactory:

public class TheObjectFactory

{

  public static T Create<T> (…)

  {

    …

  }

}

How do you implement that Create factory method so that you can pass it an arbitrary number of arguments?

The standard way to do this in .NET is a params array. It looks as follows:

public static class TheObjectFactory

{

  public static T Create<T> (params object[] args)

  {

    …

  }

}

With a params array, the C# compiler allows the Create method to be called with any number of arguments. It will wrap them up into an array and pass that array to the method at runtime. This is the approach used by factory methods in the .NET framework library, such as Activator.CreateInstance, so it can’t be wrong, can it?

Well, most of the time it works. Unless you are in one of the following situations:

  • You want to pass a single argument to the factory method and the argument is an array.
    In this case, the C# compiler thinks the array you’re passing is the params array, and it will thus not wrap it up.
    At runtime, the factory method will then look for a constructor with a signature corresponding to the array’s elements, which will either fail or find the wrong constructor.
    The workaround is to manually wrap up the array into another array and to pass that wrapper as the params array.
  • You want to pass a single argument to the factory method and the argument is null.
    Again, the C# compiler will think the null you’re passing is the params array, and again, it will not wrap it up.
    At runtime, the factory method will either throw an exception or think you’re looking for the default constructor (Activator.CreateInstance does that).
    The workaround is again to manually wrap it up.
  • You want to pass a null argument to the factory method, and there are two or more constructors accepting reference types.
    Even when your null value is wrapped up correctly, overload resolution at runtime will fail if more than one constructor accepting reference types exists.
    Of course, this must fail, after all, you cannot infer which of the constructors to call.
    However, the workaround is really awkward: you need to pass an additional Type array, which denotes the signature of the constructor to be called.

These situations tend to surface quite often, they surface only at runtime, and sometimes you might not even notice you’re calling the wrong constructor! Also, the workarounds aren’t really beautiful.

Therefore, we considered other possibilities for passing arbitrary arguments to a factory method. And we (well, Stefan, actually) had an idea: we could exploit C# generics to have the C# compiler hand over the type information together with the argument’s values. This was first implemented in a fluent interface way (With), which worked nicely but was a little unintuitive, so it got rewritten as a generic parameter object (ParamList).

ParamList is implemented as follows:

public abstract class ParamList

{

  public static ParamList Empty = new ParamListImplementation ();

 

  public static ParamList Create () { return Empty; }

 

  public static ParamList Create<A1> (A1 a1)

  {

    return new ParamListImplementation<A1> (a1);

  }

 

  public static ParamList Create<A1, A2> (A1 a1, A2 a2)

  {

    return new ParamListImplementation<A1, A2> (a1, a2);

  }

 

  public static ParamList Create<A1, A2, A3> (A1 a1, A2 a2, A3 a3)

  {

    return new ParamListImplementation<A1, A2, A3> (a1, a2, a3);

  }

 

  // More Create overloads …

  public abstract object InvokeConstructor (

      IConstructorLookupInfo constructorLookupInfo);

}

The ParamList.Create methods have overloads taking up to 20 generic arguments. When one of them is called, the C# compiler will automatically infer the generic argument types from the actual parameters specified at the call site, and the method will instantiate a specific ParamListImplementation object that not only knows the parameter values but also the parameter types inferred by the compiler.

There are variants of the ParamListImplementation classes for up to 20 generic arguments as well, and each of those implements the InvokeConstructor method. Here is one of them (from the overload with one generic argument A1):

public override object InvokeConstructor (

    IConstructorLookupInfo constructorLookupInfo)

{

  var funcDelegate =

      constructorLookupInfo.GetDelegate (typeof (Func<A1, object>));

  return funcDelegate (_a1);

}

InvokeConstructor gets passed a IConstructorLookupInfo object, which has the ability to lookup constructors with a specific signature and return a delegate calling that constructor. This is used with the actual parameter types inferred when calling the Create method, and the delegate is called with the argument passed to the method.

With ParamList, the factory can be implemented as follows:

public static class TheObjectFactory

{

  public static T Create<T> (ParamList ctorArgs)

  {

    var info = new ConstructorLookupInfo (typeof (T));

    return (T) ctorArgs.InvokeConstructor (info);

  }

}

Now, why does this help us?

First, the common usage scenarios of the factory still work. You can create a ParamList with an arbitrary number of arguments (see below about the limit of twenty), and type inference will ensure that the ParamList passes the right argument types on to the ConstructorlookupInfo.

Second, the cases I mentioned above, now either work, yield a compiler error, or at least offer a decent solution:

  • You want to pass a single argument to the factory method and the argument is an array.
    In this case, the C# compiler will infer that you are passing an array, and the ConstructorLookupInfo will look for a constructor taking an array. Works.
  • You want to pass a single argument to the factory method and the argument is null
    Here, the C# compiler will not be able to infer the type (since null does not have any ordinary type). It will will yield a compiler error, but you can cast the null value to a specific type.
  • You want to pass a null argument to the factory method, and there are two or more constructors accepting reference types.
    This must fail at runtime, but to make the call non-ambiguous, you can cast the value to one of the specific types taken by the constructors.

Since it eliminates two possibilities for silent failure and adds a decent way of removing the ambiguity for the third, we decided to go this way with re-motion’s ObjectFactory class.

But what about the limit of 20 arguments? If you really need to call a constructor with more than 20 arguments (what?!), you can use the ParamList.CreateDynamic method, which falls back to the array approach. But this really shouldn’t be needed on a regular basis.

So, to wrap this up: re-motion’s ObjectFactory has to be able to take an arbitrary number of constructor arguments. We didn’t want to go the params array route because that has a few important shortcomings. Therefore, we built a parameter object ParamList, which uses C#’s generic type inferencing features to not only collect the constructor arguments’ values but also its types. What we gained is cleaner overload resolution, fewer gotchas, and better type safety.

Written by Fabian

March 2nd, 2009 at 2:48 pm

2 Responses to 'May the Params be With you'

Subscribe to comments with RSS

  1. Well told, but I’d like to add a few details.

    The parameter resolution of params array does not only break in some scenarios, it also gives you inconsistent results in others. Consider a method that has overloads for Animal and Dog (where Dog derives from Animal), and you pass a variable of type Animal. If this variable contains a Dog at runtime, a params array-based implementation will pick the Dog overload. This might even be a good thing in some cases, as it gives us multi dispatch semantics. (You can use ParamList.CreateDynamic() whenever you need that behavior.)

    However, as C# programmers, we’re conditioned to expect overload resolution to be more deterministic. This level of dynamic behavior would therefore probably be pretty unexpected.

    We’d end up with a scenario where a call might work in some cases and fail or vary in others. (Apply Murphy’s Law: it will work in your tests and fail in production.)

    ParamList.Create(), while weaker than compile-time binding, gives you the security that what was tested once will invariably work the same way later.

    Also, I’d like to add that in many cases, passing null references happens not through the null keyword, but through variables or parameters that sometimes contain null references. In these cases, no casting would be necessary. ParamList.Create() would just work.

    And that’s basically the story of ParamList.Create(): it requires quite a bit of characters to explain the full background. But if you use it, it will just work.

    Stefan Wenig

    2 Mar 09 at 17:08

  2. Right! ParamList has so many advantages I always tend to forget to mention half of them 🙂

    Fabian Schmied

    9 Mar 09 at 10:54

Leave a Reply