Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Casting and Passing Anonymous Types

0.00/5 (No votes)
17 Jan 2008 1  
How to pass anonymous types and how to cast them

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:

  1. 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.
  2. Properties are optional. A property included in the source object doesn't necessarily have to be included in the example class, and vice-versa.
  3. 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;
   //Get constructor with lowest number of parameters and its parameters 
   var constructor = typeof(T).GetConstructors(
      BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
   ).OrderBy(c => c.GetParameters().Length).First();
   var parameters = constructor.GetParameters();
   
   //Get properties of input object
   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;
         //See if the current parameter is found as a property in the input object
         var source = sourceProperties.Find(delegate(PropertyInfo item) {
            return comparer.Compare(item.Name, parameters[i].Name) == 0;
         });
         
         //See if the property is found, is readable, and is not indexed
         if (source != null && source.CanRead && 
            source.GetIndexParameters().Length == 0) {
            //See if the types match.
            if (source.PropertyType == t) {
               //Get the value from the property in the input object and save it for use
               //in the constructor call.
               values[i] = source.GetValue(o, null);
               continue;
            }
            else {
               //See if the property value from the input object can be converted to
               //the parameter type
               try {
                  values[i] = Convert.ChangeType(source.GetValue(o, null), t);
                  continue;
               }
               catch {
                  //Impossible. Forget it then.
               } 
            }
         }
         //If something went wrong (i.e. property not found, or property isn't 
         //converted/copied), get a default value.
         values[i] = t.IsValueType ? Activator.CreateInstance(t) : null;
      }
      //Call the constructor with the collected values and return it.
      return (T) constructor.Invoke(values);
   }
   //Call the constructor without parameters and return the it.
   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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here