Partager via


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.

Comments

  • Anonymous
    September 05, 2009
    It realy nice and would be helpfull in various real application scenarios.   Thanks for such article.

  • Anonymous
    September 08, 2009
    This is a very interesting explanation.  What I would really like to see is a way to specify constraints on the dynamic itself - something like dynamic<T>.  This would allow you to pseudo-strong type a dynamic, which I think has many practical uses.  Is there anything preventing you from being able to do that?

  • Anonymous
    September 08, 2009
    Still not buying it. A dynamic constraint should mean "you can use any operator on the object, as long as the actual object supports it". C++ has it, dynamically-typed languages such as Python have it, C# should have it too.In your example List<object> is more restrictive than List<dynamic> (it only allows you to use the interface of Object on the items in the list), so the most encompassed type of List<dynamic> and List<object> is List<object>.

  • Anonymous
    September 09, 2009
    Couldn't List<dynamic> just result in compile time fluff that creates a collection type on the fly, strongly typed to dynamic that wrapps a List<object> instead?

  • Anonymous
    September 11, 2009
    @Alex it is more of a .Net constraint rather then a language constraint. Notice how it is talking about Metadata, not language as the reason for this.

  • Anonymous
    September 14, 2009
    Alex, Jeff, I think there might be some confusion about the scope of this post. I am only talking about constraints on generic type parameters--something, e.g., C++ does not have. List<dynamic> does exist in C#, and it is usable, and it basically does what you expect. You simply cannot use it in this rather narrow portion of the language. You can use it everywhere else.

  • Anonymous
    September 14, 2009
    Michael, there are a variety of meanings you could give to your suggestion of a "dynamic<T>", but none of the ones that I can think of are in the language. One of the design iterations of dynamic allowed for a mix of dynamic and static dispatch, but it didn't make it. Maybe I'll have more to say about that later.

  • Anonymous
    September 16, 2009
    Please stop ruining C#. You guys are making it more and more unreadable. A 'NO NO' in code. STOP IT.

  • Anonymous
    September 23, 2009
    I agree to a certain extent with the last comment... I do understand the benefits of what is being added and when used properly, still results in fairly readable code...Where it gets really bad, is when you have lazy devs that use generics nested soo deeply that it's really hard to see what they're trying to do.  In a lot of cases, inheritance is still better...oh well.... trade offs i guess :)I guess that's not a language or framework issue... more of a training one :)

  • Anonymous
    September 26, 2009
    I agree with the previous post. C# is now such a mess that I am seriously thinking of moving back to Java - something I never thought I'd say :(

  • Anonymous
    October 29, 2009
    I disagree with previous post. Not adding new features to C# will make it stagnant like Java. The new features are there to help in scenarios where they might be useful. No one is forcing anything on anyone so if you think these features are cluttering your code or reducing readibility/maintanibility, don't use them.

  • Anonymous
    November 10, 2009
    Chris I want you to read something.http://en.wikipedia.org/wiki/Self-documentingThat was very annoying to follow your code.