I've already written here about C#'s dynamic capabilities several times (1, 2), and sure they are pretty cool and rather astonishing the first time you hear about them. Problem is that once you've passed the initial astonishment phase (there are still quite few languages that mix the static and dynamic paradigms, ActionScript and Groovy come to mind) you intend to use these dynamic features at the same level other languages have got you used to, and it's then when you're faced with several limitation. Let's see how to (sort or) circumvent them
I'm going to focus on the use of ExpandoObject's
- Initialization syntax.
The normal way to initialize this would be something like this (notice the usage of closures so that "methods" can have access to their "this":dynamic expObj = new ExpandoObject(); expObj.Name = "Xuan"; expObj.LastName = "Arboleya"; expObj.PrettyPrint = (Func<string>)(() => "X" + expObj.Name + " " + expObj.LastName + "X"); Console.WriteLine(expObj.PrettyPrint());
When used to Object literals like the ones in JavaScript, the syntax above does not look much elegant. After several attempts involving lambdas or Dictionaries... I've concluded your best bet is to use the technique explained here. The use of params is something I was missing and that gives us a rather elegant syntax.
public static KeyValuePair<string, object> WithValue(this string key, object value) { return new KeyValuePair<string, object>(key, value); } public static ExpandoObject Init( this ExpandoObject expando, params KeyValuePair<string, object>[] values) { foreach(KeyValuePair<string, object> kvp in values) { ((IDictionary<string, Object>)expando)[kvp.Key] = kvp.Value; } return expando; } dynamic foo = new ExpandoObject().Init( "A".WithValue(true), "B".WithValue("Bar"));
- Ability to enumerate the properties: fields, methods (delegates) in a dynamic object.
This is not possible out of the box cause any class implementing the IDynamicMetaObjectProvider interface can do it in a different way, so it's the class designer who has to provide that functionality. As for ExpandoObject, given that it implements the IDictionary interface, it's a matter of doing a Casting to IDictionary (it's an Explicit Interface implementation, that's why casting to ExpandoObject is not enough) - Ability to add/remove/get access to properties using its string name. The same reasoning explained above applies here, so cast to IDictionary
Based on my explanations above, I've put together this helper class:
public static class ExpandoHelper { //all credit for these two methods to SomeDave: //http://stackoverflow.com/questions/5910331/using-collectioninitializer-syntax-with-expandoobject public static KeyValuePair<string, object> WithValue(this string key, object value) { return new KeyValuePair<string, object>(key, value); } public static ExpandoObject Init( this ExpandoObject expando, params KeyValuePair<string, object>[] values) { foreach(KeyValuePair<string, object> kvp in values) { ((IDictionary<string, Object>)expando)[kvp.Key] = kvp.Value; } return expando; } public static IEnumerable<string> GetKeys(this ExpandoObject obj) { return ((IDictionary<string, object>)obj).Keys; } public static T GetValue<T>(this ExpandoObject obj, string key) { return (T)((IDictionary<string, object>)obj)[key]; } public static void SetValue(this ExpandoObject obj, string key, Object value) { ((IDictionary<string, object>)obj)[key] = value; } }
that can be used like this:
public static void Print(dynamic d) { Console.WriteLine(d.PrettyPrint()); foreach(string key in ExpandoHelper.GetKeys(d)) Console.WriteLine(key + ": " + ExpandoHelper.GetValue<Object>(d, key)); Console.WriteLine(ExpandoHelper.GetValue<string>(d, "Name")); //Extension Methods are a compile time artifact, so, while the line below compiles, it crashes at runtime, as the dynamic dispatch mechanism can't find a GetValue property in that object //d.GetValue<string>("Name"); ExpandoHelper.SetValue(d, "Name", "Antón"); Console.WriteLine(ExpandoHelper.GetValue<string>(d, "Name")); Console.WriteLine(d.PrettyPrint()); Console.WriteLine("---------------------------"); }
A few things to note here:
- The compiler will allow us to pass a variable declared as dynamic to any method (whatever the type of the parameter that it's expecting), assign it to any variable or cast it to whatever type, but then we'll get the errors at runtime.
- We already know that Extension Methods are purely a compile time artifact, with all the binding done by the compiler. This means we can't write this:
dynamic d1;
d1.GetValue("key");
cause the dynamic dispatch the ExpandoObject won't take into account my extension methods at all.
When writing this entry I ran into this pretty interesting project, Impromptu Interface. Another interesting idea that I found is thinking about dynamic as a high level type, without a low-level correspondence.
No comments:
Post a Comment