Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failing simple test for ExpandoObject. Can anybody explain why?

Tags:

c#

dynamic

First the error message

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : 'System.Collections.Generic.List' does not contain a definition for 'First' at CallSite.Target(Closure, CallSite, Object) at System.Dynamic.UpdateDelegates.UpdateAndExecute1(CallSite site, T0 arg0) at ClaySharp.Tests.ToPropertyDictionaryTests.TestExpando() in ToPropertyDictionaryTests.cs: line 91

Test:

[Test]

public void TestExpando()
{
    dynamic root = new ExpandoObject();
    root.Name = "Name";

    var result = GetExpandos();

    root.Child = result;

    var first = root.Child.First();

    Assert.That(first.Name, Is.EqualTo("Obj1"));
}

private IEnumerable<dynamic> GetExpandos()
{
    var toReturn = new List<dynamic>();

    dynamic obj1 = new ExpandoObject();
    toReturn.Add(obj1);
    obj1.Name = "Obj1";

    dynamic obj2 = new ExpandoObject();
    toReturn.Add(obj2);
    obj2.Name = "Obj2";

    return toReturn;
}

Interesting part is that if "root" is removed from the picture, and test is performed against the "result" than it works fine.

And now for the very wierd part. Debug is set point just before "toReturn" is returned. Take a look at this, it works

?toReturn.GetType().FullName

"System.Collections.Generic.List`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"

?toReturn

Count = 2 [0]: {System.Dynamic.ExpandoObject} [1]: {System.Dynamic.ExpandoObject}

?toReturn.First()

{System.Dynamic.ExpandoObject}

And just before it is assigned to "root", still works

?result

Count = 2 [0]: {System.Dynamic.ExpandoObject} [1]: {System.Dynamic.ExpandoObject}

?result.First()

{System.Dynamic.ExpandoObject}

but after it is assigned to root, this fails

?root.Child

{System.Collections.Generic.List} [0]: {System.Dynamic.ExpandoObject} [1]: {System.Dynamic.ExpandoObject}

?root.Child.First()

like image 856
epitka Avatar asked Feb 06 '11 13:02

epitka


1 Answers

dynamic currently doesn't work well with extension methods; the compiler won't be able to "dynamically" bind to the LINQ to Objects First method at run-time when it is invoked "as" an extension-method. From the language specification:

7.6.5.2 Extension method invocations

...if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation. If expr or any of the args has compile-time type dynamic, extension methods will not apply.

To understand why, you might want to read Will the dynamic keyword in C#4 support extension methods?

Replace:

var first = root.Child.First();

with an explicit call to the static method:

var first = Enumerable.First(root.Child);

or simply be using the indexer:

var first = root.Child[0];

EDIT:

Interesting part is that if "root" is removed from the picture, and test is performed against the "result" than it works fine.

The variable result is implicitly typed to IEnumerable<dynamic>; this is its compile-time (static) type. In this case, when you do result.First(), the compiler has no problems binding to the Enumerable.First method at compile-time. If you changed the compile-time type of result to dynamic, the error would recur.

like image 93
Ani Avatar answered Oct 01 '22 20:10

Ani