Introduction
Since C# 3.0, we've had the chance to play around with anonymous types among other cool new stuff. Since I've starting working with them, I found anonymous types to be useful in more situations than just LINQ statements. For example, in JavaScript many functions may return associative arrays, and in the same way in C#, a method that may return an anonymously typed object when declaring a whole class or struct is overkill.
That's not possible, Microsoft says. You can't pass an anonymous type from one method to the next, down the call stack.
In fact, you can pass anonymous types down the call stack. You'll just have to (implicitly) cast them back to an object
. However, by then, your anonymous type becomes nearly useless.
Background
A fellow who goes by the name AlexJ (kudos for the idea!) has found otherwise. Basically what we need to do is take the returned anonymous type and cast it back to its original form. For this, he uses a generic extension method on object
that takes the type of an example object
and casts the object
to that type:
public static T CastByExample<T>(this object obj, T example) {
return (T) obj;
}
Say we've got a method that returns some anonymous type that has a property FullName
. Nothing more. Then having put that object into obj
, we could use the following call to get back to the original, strong-typed, IntelliSense-enabled object:
var v = obj.CastByExample(new { FullName = "" });
The great disadvantage of this approach is that when the number of properties, names of properties and types of properties of obj
and example do not exactly match up, a nasty InvalidCastException
is thrown at runtime.
A Better, More Flexible Approach
I wanted to get rid of as much of the required coordination between the method that returns an anonymous object
, and its consumer. Since waiting for InvalidCastException
reports from the end-user is a great way to do this, I set out to forge a method that basically copies all properties from a source object
into a new one, based on an example object
.
My design has three advantages over the initial approach:
- Property names are case-insensitive. The source
object
may include a property Foo
, while the example object
may be built to expect foo
. These two are considered the same property and Foo
is therefore copied to foo
. Good for the VB guys and gals, and doesn't break code when Fullname
gets renamed to FullName
.
- Properties are optional. A property included in the source
object
doesn't necessarily have to be included in the example class, and vice-versa.
- Property types are not explicit. The example
object
could include a property Foo
of type string
, while the source object
includes a property Foo
of type int
. The property gets converted to a string
automatically.
If a property in the resulting anonymous type cannot be set because it doesn't exist in the source object
, or because it's value cannot be converted, it gets a default value. That's 0
for integral types, null
for reference types, etc.
So without further ado, here's the code:
public static T TolerantCast<T>(this object o, T example) where T: class {
IComparer<string> comparer = StringComparer.CurrentCultureIgnoreCase;
var constructor = typeof(T).GetConstructors(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
).OrderBy(c => c.GetParameters().Length).First();
var parameters = constructor.GetParameters();
var sourceProperties = new List<PropertyInfo>(o.GetType().GetProperties());
if (parameters.Length > 0) {
var values = new object[parameters.Length];
for (int i = 0; i < values.Length; i++) {
Type t = parameters[i].ParameterType;
var source = sourceProperties.Find(delegate(PropertyInfo item) {
return comparer.Compare(item.Name, parameters[i].Name) == 0;
});
if (source != null && source.CanRead &&
source.GetIndexParameters().Length == 0) {
if (source.PropertyType == t) {
values[i] = source.GetValue(o, null);
continue;
}
else {
try {
values[i] = Convert.ChangeType(source.GetValue(o, null), t);
continue;
}
catch {
}
}
}
values[i] = t.IsValueType ? Activator.CreateInstance(t) : null;
}
return (T) constructor.Invoke(values);
}
return (T) constructor.Invoke(null);
}
A Final Note
Anonymous types are in no way a golden hammer. Only use them when you see the alternative being totally overkill. Since this approach uses reflection quite heavily, you shouldn't be using this method if CPU utilization is a bottleneck.
History
- 17th January, 2008: Initial version