传递和转换匿名类型
如何传递匿名类型以及如何转换它们
引言
自从 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
中。
我的设计具有三个优于初始方法的优势
- 属性名称不区分大小写。源
object
可能包含一个属性Foo
,而示例object
可能被构建为期望foo
。这两个被认为是相同的属性,因此Foo
被复制到foo
。这对于 VB 的朋友们来说很好,并且在Fullname
被重命名为FullName
时不会破坏代码。 - 属性是可选的。源
object
中包含的属性不一定必须包含在示例类中,反之亦然。 - 属性类型不是显式的。示例
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日:初始版本