tags:

views:

255

answers:

1

I've seen a very interesting post on Fabio Maulo's blog. Here's the code and the bug if you don't want to jump to the url. I defined a new generic class like so:

public class TableStorageInitializer<TTableEntity> where TTableEntity : class, new()
    {
        public void Initialize()
        {
            InitializeInstance(new TTableEntity());
        }

        public void InitializeInstance(dynamic entity)
        {
            entity.PartitionKey = Guid.NewGuid().ToString();
            entity.RowKey = Guid.NewGuid().ToString();
        }

    }

Note that InitializeInstance accepts one parameter, which is of type dynamic. Now to test this class, I defined another class that is nested inside my main Program class like so:

class Program
        {
            static void Main(string[] args)
            {
               TableStorageInitializer<MyClass> x = new TableStorageInitializer<MyClass>();
                x.Initialize();
            }
            private class MyClass
            {
                public string PartitionKey { get; set; }
                public string RowKey { get; set; }
                public DateTime Timestamp { get; set; }
            }
        }

Note: the inner class "MyClass" is declared private.
Now if i run this code I get a Microsoft.CSharp.RuntimeBinder.RuntimeBinderException on the line "entity.PartitionKey = Guide.NewGuid().ToString()".
The interesting part, though is that the message of the exception says "Object doesn't contain a definition for PartitionKey".
alt text

Also note that if you changed the modifier of the nested class to public, the code will execute with no problems. So what do you guys think is really happening under the hood? Please refer to any documentation -of course if this is documented anywhere- that you may find?

+4  A: 

The binder tries to work out an accessible class to treat the object as - in this case, the code making that call doesn't "know" about the MyClass class, so it doesn't "know" about PartitionKey either.

I don't know how thoroughly this is documented in the C# 4 spec - I know I've had an email conversation about it with Chris Burrows though, so the details may be somewhere on his blog :) Bear in mind that the dynamic binding has changed over time, so more recent posts are likely to be more accurate with respect to the RTM code.

I think that if you put PartitionKey into a public interface that the private class implements, that may work - but you'd have to try it.

There are various "gotchas" around dynamic typing. Explicit interface implementation has a similar problem:

public int Count(IList list)
{
   int count1 = list.Count; // Fine
   dynamic d = list;
   int count2 = d.Count; // Should work, right?
}

This will fail if you pass in an array - because although IList.Count exists, it's implemented explicitly in arrays - so it's a bit like this:

string[] array = new string[10];
Console.WriteLine(array.Count); // Won't compile

The binder tries to treat the object as its concrete type, not as IList, hence the failure...

Jon Skeet
I used to think that using dynamic is a shorthand of using plain old reflection, apparently I'm wrong coz this would work just fine if you replaced the implementation of InitializeInstance to use reflection. Would you elaborate on that, please?
Galilyou
@Jon: I'm afraid I still don't understand how arrays got away with not implementing all the interface properties (though casting it to an `IList<string>` allows you to call `Count`!?) - could you please elaborate? *(also, did you mean `d.Count` above?)*
BlueRaja - Danny Pflughoeft
Nevermind, [answered my own question](http://msdn.microsoft.com/en-us/library/aa288461%28VS.71%29.aspx).
BlueRaja - Danny Pflughoeft
@BlueRaja: I believe it is because, as Jon said, it is "implemented explicitly in arrays.""A class that implements an interface can explicitly implement a member of that interface. When a member is explicitly implemented, it cannot be accessed through a class instance, but only through an instance of the interface."Since array is an instance of an array and not of an IList, it has no implementation defined for the "Count" property.Granted, I might be glossing over a couple key points here.A good read is: --> http://msdn.microsoft.com/en-us/library/aa288461%28VS.71%29.aspx
Pandincus
@BlueRaja: Yes, I meant d.Count. Have fixed. @Galilyou: It's using reflection, but in a smarter way: it's taking account of what classes are accessible from the calling context etc.
Jon Skeet