Dynamic type parameter constraints in C# 4

What we know already

So we talked about how you can define a class that has a base class that involves dynamic, such as

 // This is OK!
class Derived : Base<dynamic>
{
}

although you cannot use dynamic alone as a base class. And we talked about how you cannot define a class that implements an interface that involves dynamic, such as

 // error CS1966: 'ThisIsAnError': cannot implement
// a dynamic interface 'ISomeInterface<dynamic>'
class ThisIsAnError : ISomeInterface<dynamic>
{
}

but that was acceptable, since you can just use object. I hope that the reasons I presented make sense, and that this situation seems fair to you. Today I am here to tell you that there is another thing you cannot do with dynamic, and it’s a little more complicated than the interface case.

What we can’t do with constraints

 class C
{
    void M<T>(T t) where T : List<dynamic> { }
}

As in the above code, you cannot specify type parameter constraints that involve dynamic. There are a few reasons why we disallow this. The first reason (and possibly the least satisfying) is that we have nowhere good to put the metadata that says the List<object> in question is really a List<dynamic>. Well, not without concocting some scheme to associate the DynamicAttribute with constraints that appear on methods and classes.

  • How would you achieve this in the example above?
  • Where does the metadata go?
  • If you have a scheme in mind, then what if there are a variety of secondary constraints that also involve dynamic? They’re probably going to be interfaces, so maybe we could just disallow them again; if not, then we need to store something somewhere and be able to say which DynamicAttributes (or, whatever you choose) go with which types.
  • Ordering is potentially fragile here, as the set of constraints is logically a set, not a list.
  • Oh, and by the way, we really do need to put these dynamic bits in metadata if we let you say them, since any overrides of your method, which may not be in your assembly, are going to inherit them.

So this is already a bunch of work and potentially a complicated metadata scheme that we’d need to support forever. That’s points against. Now the question becomes, why would someone want to write code that looks like this, and what would they expect it to do?

How these constraints might work

There are two big ways that constraints affect you and the code that you use them with. First, as a consumer, you cannot specify actual type arguments that do not satisfy the constraints for your class or method. The CLR enforces this (together with the languages). Would we be able to take advantage of dynamic constraints such as the one above for this purpose? Well, no, we wouldn’t! We wouldn’t because we are going to emit the constraint above as a List<object>, and there is no way to get the CLR to do any sort of validation that would allow the user to call M<List<dynamic>> but not M<List<object>>. Heck, the CLR doesn’t even know what List<dynamic> is! So for the consumer of your type parameter, which could be written in another language, a constraint of List<dynamic> would really be identical to a constraint of List<object>.

The second big effect you see when you use constraints like this is that in the body of M, you’d want your type parameter T to have an “effective base class” of List<dynamic>. And it’s in this behavior that we actually could affect the code a user can write. Because when the compiler compiles the body of M, above, if you had an expression that looked like t[0], we’d know it was supposed to be dynamic and we could dispatch it dynamically.

 class C
{
    void M<T>(T t) where T : List<dynamic>
    {
        // We want this to be a dynamic dispatch!
        t[0].FooBar();
    }
}

Ok, so what would you expect the effective base class of T to be in the following example?

 class C<CT> where CT : List<dynamic>
{
    void M<T>(T t) where T: List<object>, CT
    {
        // In here, is t[0] object, or is it dynamic?
    }
}

Do you see the problem here? The compiler goes to a lot of trouble to compute the effective base class of a type parameter, T in this case, and the spec says that T’s effective base class is supposed to be the “most encompassed type” of the constraints given, and it comes down to the question of what the most encompassed type of List<dynamic> and List<object> is. I hope you can convince yourself that there’s no good answer that a large fraction of users might expect or be happy with.

Another wrinkle

So that’s why we don’t let you specify constraints that have dynamic in them. But we’re not done yet! Just because we don’t let you say the constraints, doesn’t mean you can’t sneak them in. Check this out:

 class Base<CT>
{
    public virtual void M<T>(T t) where T : CT { }
}

class C : Base<List<dynamic>>
{
    public override void M<T>(T t) { }
}

Ok. What’s the effective base class of T in M in C? This is a standard trick to be able to get constraints that the language won’t let you write. In this case, the constraint the language won’t let me write is List<dynamic>, but it could just as well have been sealed or a value type, such as int. Since overrides inherit the constraints of their parents, the result is that the effective base class of T in M in C is going to be List<dynamic>, but this poses a problem since we still have no way to emit it, which we need.

So there’s another rule to handle this case, which is that when we compute the effective base class of your type parameters, they will never include dynamic. They will always be the most-reduced type, where any dynamics have been reduced to object. The value of this rule is that it’s rather uncomplicated and the potential for confusion is relatively low compared to compromises we could have come up with. Just remember this: when you use them in the body of your method or class, type parameters never have dynamic in them!

Just to summarize

There are two rules in the language to remember, and they are:

  1. Type parameter constraint clauses can never contain a type that includes dynamic; this is a compilation error.
  2. Neither the effective base class nor any member of the effective interface set will be computed to be a type that involves dynamic; these types will always contain object in the place of dynamic.

This is all about constraints on type parameters, by the way. You can still use dynamic as a type argument, for instance if you want a local or a field of type List<dynamic>. This is because you are using List<>, not defining it. The code that defines List<> has no idea you put a dynamic in there.