What Are the True Benefits of Expandoobject

What are the true benefits of ExpandoObject?

Since I wrote the MSDN article you are referring to, I guess I have to answer this one.

First, I anticipated this question and that's why I wrote a blog post that shows a more or less real use case for ExpandoObject: Dynamic in C# 4.0: Introducing the ExpandoObject.

Shortly, ExpandoObject can help you create complex hierarchical objects. For example, imagine that you have a dictionary within a dictionary:

Dictionary<String, object> dict = new Dictionary<string, object>();
Dictionary<String, object> address = new Dictionary<string,object>();
dict["Address"] = address;
address["State"] = "WA";
Console.WriteLine(((Dictionary<string,object>)dict["Address"])["State"]);

The deeper the hierarchy, the uglier the code. With ExpandoObject, it stays elegant and readable.

dynamic expando = new ExpandoObject();
expando.Address = new ExpandoObject();
expando.Address.State = "WA";
Console.WriteLine(expando.Address.State);

Second, as was already pointed out, ExpandoObject implements INotifyPropertyChanged interface which gives you more control over properties than a dictionary.

Finally, you can add events to ExpandoObject like here:

class Program
{
static void Main(string[] args)
{
dynamic d = new ExpandoObject();

// Initialize the event to null (meaning no handlers)
d.MyEvent = null;

// Add some handlers
d.MyEvent += new EventHandler(OnMyEvent);
d.MyEvent += new EventHandler(OnMyEvent2);

// Fire the event
EventHandler e = d.MyEvent;

e?.Invoke(d, new EventArgs());
}

static void OnMyEvent(object sender, EventArgs e)
{
Console.WriteLine("OnMyEvent fired by: {0}", sender);
}

static void OnMyEvent2(object sender, EventArgs e)
{
Console.WriteLine("OnMyEvent2 fired by: {0}", sender);
}
}

Also, keep in mind that nothing is preventing you from accepting event arguments in a dynamic way. In other words, instead of using EventHandler, you can use EventHandler<dynamic> which would cause the second argument of the handler to be dynamic.

ExpandoObject - why a Type behaves differently?

This is because the variable expObj4 is declared as ExpandoObject and not as dynamic. This is an important difference.

Try this:

dynamic a = new ExpandoObject();
a.Name = "Test";

This compiles, but the following doesn't:

ExpandoObject a = new ExpandoObject();
a.Name = "Test";

you get this:

CS1061 'ExpandoObject' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'ExpandoObject' could be found

The variables you have that are related to this are:

  • expandoObj1 - dynamic
  • expandoObj2 - dynamic
  • expObj1 - dynamic
  • expObj2 - dynamic
  • expObj3 - dictionary, but you use dictionary access here, not dot-access

The magic "let's see if we can access the thing at runtime" code of the compiler only kicks in if the expression or variable is dynamic. ExpandoObject is just a type that supports this.

Can an ExpandoObject be a list?


DynamicObject has TryGetIndex so I assume an ExpandoObject can have an indexer

No, it can't. Just try it, it returns a RuntimeBinderException:

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

An ExpandoObject is not a list; it's more like a dictionary.

Would it be necessary to carefully construct a list-like instance of expando by dynamically supplying the implementation of an indexing method or otherwise create a non-expando DynamicObject to hold the list and invoke the correctly methods from dynamic method calls?

Well, you could create a custom DynamicObject that behaves like a list, but I don't see any benefit in doing that: you might as well use a normal list.

Why does ExpandoObject not work as expected?

Deleting the hidden "SolutionName.suo" file in the solution directory fixed this problem for me.

I still have no clue why it occured, though.

Edit:
Andras Zoltan, who deleted his answer, guessed correctly. I have had "Break on all Exceptions" enabled and was being stupid. =)

Exposing properties of an ExpandoObject

Implementing ICustomTypeDescriptor actually isn't all that hard. Here is some sample code I adapted from some work I did with WinForms property grids (which uses TypeDescriptor and PropertyDescriptor). The trick is to also implement an appropriate PropertyDescriptor class that you can pass back from ICustomTypeDescriptor.GetProperties(). Thankfully the ExpandoObject makes this pretty simple by implementing IDictionary<string, object> for dynamic retrieval of it's keys and values. Keep in mind that this may or may not work correctly (I haven't tested it) and it probably won't work for ExpandoObjects with lots of nested properties.

public class ExpandoTypeDescriptor : ICustomTypeDescriptor
{
private readonly ExpandoObject _expando;

public ExpandoTypeDescriptor(ExpandoObject expando)
{
_expando = expando;
}

// Just use the default behavior from TypeDescriptor for most of these
// This might need some tweaking to work correctly for ExpandoObjects though...

public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}

public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}

public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}

public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}

EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}

public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}

public object GetPropertyOwner(PropertyDescriptor pd)
{
return _expando;
}

public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}

public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}

public PropertyDescriptor GetDefaultProperty()
{
return null;
}

// This is where the GetProperties() calls are
// Ignore the Attribute for now, if it's needed support will have to be implemented
// Should be enough for simple usage...

PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
}

public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
// This just casts the ExpandoObject to an IDictionary<string, object> to get the keys
return new PropertyDescriptorCollection(
((IDictionary<string, object>)_expando).Keys
.Select(x => new ExpandoPropertyDescriptor(((IDictionary<string, object>)_expando), x))
.ToArray());
}

// A nested PropertyDescriptor class that can get and set properties of the
// ExpandoObject dynamically at run time
private class ExpandoPropertyDescriptor : PropertyDescriptor
{
private readonly IDictionary<string, object> _expando;
private readonly string _name;

public ExpandoPropertyDescriptor(IDictionary<string, object> expando, string name)
: base(name, null)
{
_expando = expando;
_name = name;
}

public override Type PropertyType
{
get { return _expando[_name].GetType(); }
}

public override void SetValue(object component, object value)
{
_expando[_name] = value;
}

public override object GetValue(object component)
{
return _expando[_name];
}

public override bool IsReadOnly
{
get
{
// You might be able to implement some better logic here
return false;
}
}

public override Type ComponentType
{
get { return null; }
}

public override bool CanResetValue(object component)
{
return false;
}

public override void ResetValue(object component)
{
}

public override bool ShouldSerializeValue(object component)
{
return false;
}

public override string Category
{
get { return string.Empty; }
}

public override string Description
{
get { return string.Empty; }
}
}
}

ExpandoObject C# IL

I fixed this by adding restrictedSkipVisibility to the DynamicMethod Constructor.

var method = new DynamicMethod( "", typeof( object ), new Type[ 0 ], true  );
var emiter = method.GetILGenerator();
var expando = emiter.DeclareLocal( typeof( ExpandoObject ) );
emiter.Emit( OpCodes.Newobj, typeof( ExpandoObject ).GetConstructors()[ 0 ] );
emiter.Emit( OpCodes.Stloc, expando );

var value = emiter.DeclareLocal( typeof( int ) );
emiter.Emit( OpCodes.Ldc_I4_1 );
emiter.Emit( OpCodes.Stloc, value );

emiter.Emit( OpCodes.Ldloc, expando );
emiter.Emit( OpCodes.Ldnull );
emiter.Emit( OpCodes.Ldc_I4, -1 );
emiter.Emit( OpCodes.Ldloc, value );
if ( value.LocalType.IsValueType )
emiter.Emit( OpCodes.Box, value.LocalType );
else
{
emiter.Emit( OpCodes.Castclass, typeof( object ) );
}
emiter.Emit( OpCodes.Ldstr, "Test" );
emiter.Emit( OpCodes.Ldc_I4_0 );
emiter.Emit( OpCodes.Ldc_I4_1 );
emiter.Emit( OpCodes.Call, typeof( ExpandoObject ).GetMethod( "TrySetValue", BindingFlags.Instance | BindingFlags.NonPublic ) );

emiter.Emit( OpCodes.Ldloc, expando );
emiter.Emit( OpCodes.Ret );
var @delegate = (Func<dynamic>)method.CreateDelegate( typeof( Func<dynamic> ) );

var result = @delegate().Test;

Dynamically adding properties to an ExpandoObject


dynamic x = new ExpandoObject();
x.NewProp = string.Empty;

Alternatively:

var x = new ExpandoObject() as IDictionary<string, Object>;
x.Add("NewProp", string.Empty);


Related Topics



Leave a reply



Submit