Restricting a generic type parameters to have a specific constructor

Kirk Woll’s quote from me of course is all the justification that is required; we are not required to provide a justification for features not existing. Features have enormous costs.

However, in this specific case I can certainly give you some reasons why I would push back on the feature if it came up in a design meeting as a possible feature for a future version of the language.

To start with: consider the more general feature. Constructors are methods. If you expect there to be a way to say “the type argument must have a constructor that takes an int” then why is it not also reasonable to say “the type argument must have a public method named Q that takes two integers and returns a string?”

string M<T>(T t) where T has string Q(int, int)
{
    return t.Q(123, 456);
}

Does that strike you as a very generic thing to do? It seems counter to the idea of generics to have this sort of constraint.

If the feature is a bad idea for methods, then why is it a good idea for methods that happen to be constructors?

Conversely, if it is a good idea for methods and constructors, then why stop there?

string M<T>(T t) where T has a field named x of type string
{
    return t.x;
}

I say that we should either do the whole feature or don’t do it at all. If it is important to be able to restrict types to have particular constructors, then let’s do the whole feature and restrict types on the basis of members in general and not just constructors.

That feature is of course a lot more expensive to design, implement, test, document and maintain.

Second point: suppose we decided to implement the feature, either the “just constructors” version or the “any member” version. What code do we generate? The thing about generic codegen is that it has been carefully designed so that you can do the static analysis once and be done with it. But there is no standard way to describe “call the constructor that takes an int” in IL. We would have to either add a new concept to IL, or generate the code so that the generic constructor call used Reflection.

The former is expensive; changing a fundamental concept in IL is very costly. The latter is (1) slow, (2) boxes the parameter, and (3) is code that you could have written yourself. If you’re going to use reflection to find a constructor and call it, then write the code that uses reflection to find a constructor and call it. If this is the code gen strategy then the only benefit that the constraint confers is that the bug of passing a type argument that does not have a public ctor that takes an int is caught at compile time instead of runtime. You don’t get any of the other benefits of generics, like avoiding reflection and boxing penalties.

Leave a Comment