57

Something caught me by surprise when looking into C# dynamics today (I've never used them much, but lately I've been experimenting with the Nancy web framework). I found that I couldn't do this:

dynamic expando = new ExpandoObject();

expando.name = "John";

Console.WriteLine(expando["name"]);

The last line throws an exception:

Cannot apply indexing with [] to an expression of type 'System.Dynamic.ExpandoObject'

I understand the error message, but I don't understand why this is happening. I have looked at the documentation for ExpandoObject and it explicitly implements IDictionary<,> and thus has a this.[index] method (MSDN). Why can't I call it?

Of course, there's nothing to stop me from downcasting the ExpandoObject to a dictionary manually and then indexing into it, but that kind of defies the point; it also doesn't explain how the Expando was able to hide the method of one of its interfaces.

What's going on here?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Richiban
  • 5,569
  • 3
  • 30
  • 42
  • 3
    Explicit interface implementations can only be accessed if the object is cast to that interface, it is not something particular to ExpanoObject. – Ben Robinson Nov 06 '14 at 11:41
  • 1
    Relevant source code: http://referencesource.microsoft.com/#System.Core/Microsoft/Scripting/Actions/ExpandoObject.cs,552 – Jeroen Vannevel Nov 06 '14 at 11:43

2 Answers2

80

how the Expando was able to hide the method of one of its interfaces.

Because as you correctly found out in the documentation, the indexer is an explicit interface implementation. From Explicit Interface Implementation Tutorial:

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.

This means you'll have to cast the reference to the interface to access it:

((IDictionary<String, Object>)expando)["name"]
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • Thanks @CodeCaster. I knew about explicit interface implementation, but I thought it was just for implementing two members with the same name; I'd never seen it used merely to hide a member before. Do you know why they've done this with ExpandoObject? – Richiban Nov 06 '14 at 11:59
  • 1
    @Richiban one can only guess, but see [Marc Gravell's answer on a similar question](http://stackoverflow.com/questions/280495/why-to-use-explicit-interface-implementation-to-invoke-a-protected-method): _"to keep the core public API clean"_. – CodeCaster Nov 06 '14 at 12:04
  • 11
    This seems to defeat the purpose of even using an ExpandoObject. May as well just declare it as a Dictionary. – Triynko Jan 18 '16 at 06:21
  • @Triynko they created ExpandoObject for the ASP.NET MVC viewbag, it's one of the most useless types anywhere else. – Chris Marisic Apr 27 '16 at 19:18
  • 1
    @Triynko I'm surprised that your comment has this many likes. ExpandoObject in this context can be used both as `dynamic` and `Dictionary` you can assign a field dynamically `exp.MyField = 3` and read it as a dictionary value `((IDictionary)exp)["MyField"]` or vice versa. There is a situation in my project which can only be solved using ExpandoObjects. – Bamdad May 13 '21 at 12:39
3

Use this factory class to create ExpandoObjects! Then use HasProperty("prop name") or GetValue("prop name")

void Main()
{
    dynamic _obj = ExpandoObjectFactory.Create();
    if (_obj.HasProperty("Foo") == false)
    {
        _obj.Foo = "Foo";
    }
    Console.WriteLine(_obj); // Foo;
    object bar = _obj.GetValue("Bar");
    Console.WriteLine(bar); // null
}

public static class ExpandoObjectFactory
{
    public static ExpandoObject Create()
    {
        dynamic expandoObject = new ExpandoObject();
        expandoObject.HasProperty = new Func<string, bool>((string name) => ((IDictionary<string, object>)expandoObject).ContainsKey(name));
        expandoObject.GetValue = new Func<string, object>(delegate (string name)
        {
            ((IDictionary<string, object>)expandoObject).TryGetValue(name, out object value);
            return value;
        });
        return expandoObject;
    }
}