The dynamic type in C# 4.0 is a welcome addition to the language. One thing I’ve been doing a lot with it is to remove explicit Reflection code that’s often necessary when you ‘dynamically’ need to walk and object hierarchy. In the past I’ve had a number of ReflectionUtils that used string based expressions to walk an object hierarchy. With the introduction of dynamic much of the ReflectionUtils code can be removed for cleaner code that runs considerably faster to boot. The old Way - Reflection Here’s a really contrived example, but assume for a second, you’d want to dynamically retrieve a Page.Request.Url.AbsoluteUrl based on a Page instance in an ASP.NET Web Page request. The strongly typed version looks like this: string path = Page.Request.Url.AbsolutePath;
Now assume for a second that Page wasn’t available as a strongly typed instance and all you had was an object reference to start with and you couldn’t cast it (right I said this was contrived :-))
If you’re using raw Reflection code to retrieve this you’d end up writing 3 sets of Reflection calls using GetValue(). Here’s some internal code I use to retrieve Property values as part of ReflectionUtils:
/// <summary>
/// Retrieve a property value from an object dynamically. This is a simple version
/// that uses Reflection calls directly. It doesn't support indexers.
/// </summary>
/// <param name="instance">Object to make the call on</param>
/// <param name="property">Property to retrieve</param>
/// <returns>Object - cast to proper type</returns>
public static object GetProperty(object instance, string property)
{
return instance.GetType().GetProperty(property, ReflectionUtils.MemberAccess).GetValue(instance, null);
}
If you want more control over properties and support both fields and properties as well as array indexers a little more work is required:
/// <summary>
/// Parses Properties and Fields including Array and Collection references.
/// Used internally for the 'Ex' Reflection methods.
/// </summary>
/// <param name="Parent"></param>
/// <param name="Property"></param>
/// <returns></returns>
private static object GetPropertyInternal(object Parent, string Property)
{
if (Property == "this" || Property == "me")
return Parent;
object result = null;
string pureProperty = Property;
string indexes = null;
bool isArrayOrCollection = false;
// Deal with Array Property
if (Property.IndexOf("[") > -1)
{
pureProperty = Property.Substring(0, Property.IndexOf("["));
indexes = Property.Substring(Property.IndexOf("["));
isArrayOrCollection = true;
}
// Get the member
MemberInfo member = Parent.GetType().GetMember(pureProperty, ReflectionUtils.MemberAccess)[0];
if (member.MemberType == MemberTypes.Property)
result = ((PropertyInfo)member).GetValue(Parent, null);
else
result = ((FieldInfo)member).GetValue(Parent);
if (isArrayOrCollection)
{
indexes = indexes.Replace("[", string.Empty).Replace("]", string.Empty);
if (result is Array)
{
int Index = -1;
int.TryParse(indexes, out Index);
result = CallMethod(result, "GetValue", Index);
}
else if (result is ICollection)
{
if (indexes.StartsWith("\""))
{
// String Index
indexes = indexes.Trim('\"');
result = CallMethod(result, "get_Item", indexes);
}
else
{
// assume numeric index
int index = -1;
int.TryParse(indexes, out index);
result = CallMethod(result, "get_Item", index);
}
}
}
return result;
}
/// <summary>
/// Returns a property or field value using a base object and sub members including . syntax.
/// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company")
/// This method also supports indexers in the Property value such as:
/// Customer.DataSet.Tables["Customers"].Rows[0]
/// </summary>
/// <param name="Parent">Parent object to 'start' parsing from. Typically this will be the Page.</param>
/// <param name="Property">The property to retrieve. Example: 'Customer.Entity.Company'</param>
/// <returns></returns>
public static object GetPropertyEx(object Parent, string Property)
{
Type type = Parent.GetType();
int at = Property.IndexOf(".");
if (at < 0)
{
// Complex parse of the property
return GetPropertyInternal(Parent, Property);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string main = Property.Substring(0, at);
string subs = Property.Substring(at + 1);
// Retrieve the next . section of the property
object sub = GetPropertyInternal(Parent, main);
// Now go parse the left over sections
return GetPropertyEx(sub, subs);
}
As you can see there’s a fair bit of code involved into retrieving a property or field value reliably especially if you want to support array indexer syntax. This method is then used by a variety of routines to retrieve individual properties including one called GetPropertyEx() which can walk the dot syntax hierarchy easily.
Anyway with ReflectionUtils I can retrieve Page.Request.Url.AbsolutePath using code like this:
string url = ReflectionUtils.GetPropertyEx(Page, "Request.Url.AbsolutePath") as string;
This works fine, but is bulky to write and of course requires that I use my custom routines. It’s also quite slow as the code in GetPropertyEx does all sorts of string parsing to figure out which members to walk in the hierarchy.
Enter dynamic – way easier!
.NET 4.0’s dynamic type makes the above really easy. The following code is all that it takes:
object objPage = Page; // force to object for contrivance :)
dynamic page = objPage; // convert to dynamic from untyped object
string scriptUrl = page.Request.Url.AbsolutePath;
The dynamic type assignment in the first two lines turns the strongly typed Page object into a dynamic. The first assignment is just part of the contrived example to force the strongly typed Page reference into an untyped value to demonstrate the dynamic member access. The next line then just creates the dynamic type from the Page reference which allows you to access any public properties and methods easily. It also lets you access any child properties as dynamic types so when you look at Intellisense you’ll see something like this when typing Request.:
In other words any dynamic value access on an object returns another dynamic object which is what allows the walking of the hierarchy chain.
Note also that the result value doesn’t have to be explicitly cast as string in the code above – the compiler is perfectly happy without the cast in this case inferring the target type based on the type being assigned to. The dynamic conversion automatically handles the cast when making the final assignment which is nice making for natural syntnax that looks *exactly* like the fully typed syntax, but is completely dynamic.
Note that you can also use indexers in the same natural syntax so the following also works on the dynamic page instance:
string scriptUrl = page.Request.ServerVariables["SCRIPT_NAME"];
The dynamic type is going to make a lot of Reflection code go away as it’s simply so much nicer to be able to use natural syntax to write out code that previously required nasty Reflection syntax.
Another interesting thing about the dynamic type is that it actually works considerably faster than Reflection. Check out the following methods that check performance:
void Reflection()
{
Stopwatch stop = new Stopwatch();
stop.Start();
for (int i = 0; i < reps; i++)
{
// string url = ReflectionUtils.GetProperty(Page,"Title") as string;// "Request.Url.AbsolutePath") as string;
string url = Page.GetType().GetProperty("Title", ReflectionUtils.MemberAccess).GetValue(Page, null) as string;
}
stop.Stop();
Response.Write("Reflection: " + stop.ElapsedMilliseconds.ToString());
}
void Dynamic()
{
Stopwatch stop = new Stopwatch();
stop.Start();
dynamic page = Page;
for (int i = 0; i < reps; i++)
{
string url = page.Title; //Request.Url.AbsolutePath;
}
stop.Stop();
Response.Write("Dynamic: " + stop.ElapsedMilliseconds.ToString());
}
The dynamic code runs in 4-5 milliseconds while the Reflection code runs around 200+ milliseconds! There’s a bit of overhead in the first dynamic object call but subsequent calls are blazing fast and performance is actually much better than manual Reflection.
Dynamic is definitely a huge win-win situation when you need dynamic access to objects at runtime.© Rick Strahl, West Wind Technologies, 2005-2010Posted in .NET CSharp