65.9K
CodeProject 正在变化。 阅读更多。
Home

传递和转换匿名类型

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (3投票s)

2008 年 1 月 17 日

CPOL

3分钟阅读

viewsIcon

70859

如何传递匿名类型以及如何转换它们

引言

自从 C# 3.0 以来,我们有机会尝试使用匿名类型以及其他很酷的新功能。自从我开始使用它们以来,我发现匿名类型比仅仅在 LINQ 语句中使用更有用。例如,在 JavaScript 中,许多函数可能返回关联数组,同样在 C# 中,当声明整个类或结构时,可能返回匿名类型对象的 方法 显得有点过分。

微软说,这是不可能的。你不能将匿名类型从一个方法传递到下一个方法,沿着调用堆栈传递。

事实上,你可以将匿名类型沿着调用堆栈传递。你只需要(隐式地)将它们转换回 object。然而,到那时,你的匿名类型几乎变得毫无用处。

背景

一个名叫 AlexJ(感谢这个想法!)的人发现了另一种方法。基本上,我们需要做的是获取返回的匿名类型并将其转换回其原始形式。为此,他使用了一个在 object 上的泛型扩展方法,该方法接受一个示例 object 的类型,并将 object 转换为该类型

public static T CastByExample<T>(this object obj, T example) { 
    return (T) obj; 
} 

假设我们有一个方法,该方法返回一个匿名类型,该类型具有一个名为 FullName 的属性。仅此而已。然后,将该对象放入 obj 中后,我们可以使用以下调用来返回到原始的、强类型的、支持 IntelliSense 的对象

var v = obj.CastByExample(new { FullName = "" });

这种方法的主要缺点是,当 obj 和 example 的属性数量、属性名称和属性类型不完全匹配时,运行时会抛出一个令人讨厌的 InvalidCastException

一个更好、更灵活的方法

我希望摆脱返回匿名 object 的方法与其使用者之间所需的协调。由于等待来自最终用户的 InvalidCastException 报告是一种很好的方法,所以我着手创建一个方法,该方法基本上基于一个示例 object,将所有属性从一个源 object 复制到一个新的 object 中。

我的设计具有三个优于初始方法的优势

  1. 属性名称不区分大小写。源 object 可能包含一个属性 Foo,而示例 object 可能被构建为期望 foo。这两个被认为是相同的属性,因此 Foo 被复制到 foo。这对于 VB 的朋友们来说很好,并且在 Fullname 被重命名为 FullName 时不会破坏代码。
  2. 属性是可选的。源 object 中包含的属性不一定必须包含在示例类中,反之亦然。
  3. 属性类型不是显式的。示例 object 可以包含一个类型为 string 的属性 Foo,而源 object 包含一个类型为 int 的属性 Foo。属性会自动转换为 string

如果结果匿名类型中的一个属性无法设置,因为它在源 object 中不存在,或者因为它的值无法转换,则它会获得一个默认值。对于整数类型,它是 0 ,对于引用类型,它是 null ,等等。

所以,事不宜迟,这里是代码

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);
}

最后说明

匿名类型绝不是一把金色的锤子。只有在您认为替代方案完全过分时才使用它们。由于这种方法大量使用反射,如果 CPU 利用率成为瓶颈,您不应该使用此方法。

历史

  • 2008年1月17日:初始版本
© . All rights reserved.