次の方法で共有


C# “dynamic,” Part VII

It has been a while since I posted anything here, and I have the same excuse everyone else does. Work, holidays, and generally being pretty busy. But enough of that, it’s February (!!!) and that means we’re getting closer to shipping C# 4.0 and dynamic and making all of this a much less abstract exercise for everyone who wants to see it and use it.

But let’s answer the question I left off with last time.

Implementing dynamic interfaces

So, I asked last time, can you implement IEnumerable<dynamic>?

Well, to begin with, we established last time that IEnumerable<dynamic> is a real type. It exists! It derives from IEnumerable, and it has an extra method called GetEnumerator that returns an IEnumerator<dynamic>, just like any other constructed generic type would have. It’s different from IEnumerable<object>, although there is a “structural conversion” between the two that I mentioned last time.

But the question is, can you implement it? In other words, what happens when I try to compile the following, and if successful, what does it do?

 public class C : IEnumerable<dynamic>
{
  public IEnumerator<dynamic> GetEnumerator() 
  {
      return null; 
  }
  ...
}

Before we answer that directly, let’s talk about what it means when you say that, in C#.

The first thing implementing an interface means is: The compiler will verify that you have implemented, in class C, all of the methods that are defined in your interface, and it will make sure that they are virtual and marked as overriding the interface methods, if necessary.

In this example, GetEnumerator does in fact qualify as the implementation of the interface method GetEnumerator (one of them, anyway—IEnumerable is confusing). But you know what? So would have this method if I had returned IEnumerable<object>. It’s not because there is a conversion between the two (though there is, and it’s the structural one I mentioned in part VI), but it’s because when we look at method overrides and overloads, we treat object and dynamic as the same!

Have I mentioned that bit before? It’s a little weird, but if you think about it for a moment, it’s clear why. When we emit metadata, dynamic is going to come out as object anyway. So, what if you tried to define a class like this:

 public class C
{
  public void M(object x) { }
  public void M(dynamic x) { }
}

Well... We could never emit that, because the M’s would collide, so the language can’t allow overloads that differ only in dynamic/object. Similarly, if you try to override a method and it differs only in dynamic/object, that’s ok! In other words, in the interface example, that GetEnumerator would have to work regardless of whether it returned IEnumerator<dynamic> or IEnumerator<object>. And also, either of those two methods would have worked if the interface being implemented was IEnumerable<dynamic> or IEnumerable<object>.

Let’s leave that alone for the moment, though, and talk about...

The second thing implementing an interface means: You get a new implicit conversion from your type to the interface. This is clear, right? If I implement IEnumerable<dynamic>, I should now be able to say such things as

 IEnumerable<dynamic> ie = new C();

Those are the two big things that I would achieve, were I able to implement IEnumerable<dynamic>. And now let me answer the original question: No. You cannot implement an interface that includes dynamic.

Why?

Well, for one thing, it doesn’t actually give you anything that you didn’t already have. The first thing and the second thing are already there if you implemented IEnumerable<object>. In that case, you still would have been able to define GetEnumerator the way we did, and you still can convert C to IEnumerable<dynamic> (again, because of the structural conversions). Think of it this way: if anyone ever looks directly at your type C, they are never going to “see” what interfaces you implement. They only “see” them when they cast, and at that point, your IEnumerable<dynamic> didn’t do them any good.

That’s a fine reason, but you might respond, why not let me do this anyway? Why impose this limitation that seems artificial? Good question. I encountered this for the first time when I was trying to get the compiler to emit these things, and I realized very quickly that there was no where for me to emit the [Dynamic] attribute that we use to mark dynamic types. The metadata team reported that a reading of the CLI spec seemed to indicate that the tables for interface implementations and custom attributes might have permitted it, but anyway no one we know of has ever done this, and it would have been effort expended. We have priorities and a limited budget of time, and this didn’t make the cut.

An alternative suggestion might be to allow you to say IEnumerable<dynamic> but really emit IEnumerable<object>, because, after all, they are the same in this context anyway. This option was rejected mostly because it’s confusing to users and the tools. Round-tripping would break, so that if you looked at your type in the project or solution that defines it, it would appear to be one thing, and if you looked at it from the perspective of a project that merely references it, it would be something else. Not good.

So you can’t do this. The error is something like:

 error CSXXXX: 'C': cannot implement a dynamic interface 'System.Collections.Generic.IEnumerable'

There’s more! I haven’t even talked about implementing Base<dynamic>. Next time!

Previous posts in this series: Part VI, Part V, Part IV, Part III, Part II, Part I

Comments

  • Anonymous
    February 04, 2009
    By the same logic, wouldn't returning dynamic mean nothing more than returning object? Will "dynamic" as a return value be allowed?
  • Anonymous
    February 05, 2009
    The comment has been removed
  • Anonymous
    February 10, 2009
    The comment has been removed
  • Anonymous
    April 22, 2009
    Ok, where were we? We have this new type called dynamic, and it’s used to tell the compiler to defer