# 原则39:使用动态对泛型类型参数的运行时类型的利用
**By D.S.Qiu**
**尊重他人的劳动,支持原创,转载请注明出处:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
System.Linq.Enumerable.Cast<T> 强制序列的每个对象转化为目标类型 T 。这是框架的一部分,所以 LINQ 查询中的 IEnumerable (而不是 IEnumerable<T> )才能使用。 Cast<T> 是一个没有约束的泛型方法。这就是限制类型转换使用它。如果你理解 Cast<T> 的这个限制,你会发现你自己想的却不能工作。现实中,它就是该本来那样工作,而不是你期望的那样。我们检查他们的内部工作和限制。然后,你就可以很容易创建不同的版本并像你期望一样。
问题的根源在于,事实上 Cast<T> 在没有任何 T 的知识的清下编译成 MISL ,所以 T 必须是继承于 System.Object 。因此,它只能使用定义在 System.Object 的函数。查看下面的这个类:
```
public class MyType
{
public String StringMember { get; set; }
public static implicit operator String(MyType aString)
{
return aString.StringMember;
}
public static implicit operator MyType(String aString)
{
return new MyType { StringMember = aString };
}
}
```
查看原则28就知道为什么转换操作符那么不好;然而,用户定义的转换操作符就是这个问题的关键。考虑这段代码(假设 GetSomeStrings() 返回字符串序列):
```
var answer1 = GetSomeStrings().Cast<MyType>();
try
{
foreach (var v in answer1)
Console.WriteLine(v);
}
catch (InvalidCastException)
{
Console.WriteLine("Cast Failed!");
}
```
在开始遍历元素之前,你可能希望 GetSomeStrings().Cast<MyType>() 已经使用定义在 MyType 的隐式转换操作符对每个 string 正确转换为 MyType 。现在你知道它并没有,它会抛出 InvalidCastException 。
上面的代码和下面使用查询表达式是一样的构造:
```
var answer2 = from MyType v in GetSomeStrings()
select v;
try
{
foreach (var v in answer2)
Console.WriteLine(v);
}
catch (InvalidCastException)
{
Console.WriteLine("Cast failed again");
}
```
在循环体声明的变量被编译器调用 Cast<MyType> 。同样,它会抛出 InvalidCastException 。以下面的方式进行重构,就能工作:
```
var answer3 = from v in GetSomeStrings()
select (MyType)v;
foreach (var v in answer3)
Console.WriteLine(v);
```
有什么不同?前面的两个版本使用 Cast<T>() 不能工作,这个版本能工作是因为 lambda 的 Select() 的参数使用了强制类型转换。 Cast<T> 不可能在运行时访问参数类型任何自定义的转换操作符。只有引用转换或封箱转换才能使用转换操作符。只有当 is 操作符成功时,引用转换才会成功(查看原则3)。封箱转换是将值类型转换引用类型反之亦然(查看原则45)。 Cast<T> 不能访问用户定义的转换操作符因为它只能假设 T 包含定义在 System.Object 的成员。System.Object 没有包含任何自定义的转换操作符,所以这些都是不合格的。使用 Select<T> 成功时因为 lambda 使用 Select() 的输入参数类型是 string 。这就能使用定义在 MyType 的转换操作符。
我已经在前面指出过,我常把转换操作符看着是代码的小菜。偶尔,它们是有用的,但是静态是引起比它们的价值更多的问题。如果没有转换操作符,这里就不会有开发者写些不能工作的示例代码。
当然,如果我建议不要使用转换操作符,我应该会给一个替代的解决方案。 MyType 已经包含可读/写的存储 string 的属性,所以我只是把转换操作符去掉并使用构造函数:
```
var answer4 = GetSomeStrings().
Select(n => new MyType { StringMember = n });
var answer5 = from v in GetSomeStrings()
select new MyType { StringMember = v };
```
这样,我只需要为 MyType 创建不同的构造函数。当然,这只是针对 Cast<T> 的限制。既然你已经明白为什么这些限制会存在,现在就该写一个不同方法绕过这个限制。诀窍就是编写一个泛型方法并利用运行时形象进行类型转换。
你可能写了一页又一页基于反射的代码以检查哪些转换是可用的,并执行那些转换返回恰当的类型。你可以那样做,但是这是浪费。相反, C#4.0 ,动态做了所有繁重的活。你只需要简单 Convert<T> 就可以得到你期望的:
```
public static IEnumerable<TResult> Convert<TResult>( this System.Collections.IEnumerable sequence)
{
foreach (object item in sequence)
{
dynamic coercion = (dynamic)item;
yield return (TResult)coercion;
}
}
```
现在,只要从源类型到目标类型的转换(隐式或显示),转换就会工作。仍然有强制类型转换会涉及到,所有运行时失败还是有可能发生。Convert<T> 与 Cast<T> 的比较中,ConverT<T> 适用于更多的类型转换的情况,但是它需要做更多工作。作为一个开发者,你应该更多关心用户需要我们创建什么代码而不只是我们自己的代码。 Convert<T> 可以通过整个测试:
```
var convertedSequence = GetSomeStrings().Convert<MyType>();
```
Cast<T> 像其他泛型方法一样,编译时对参数类型只有有限掌握。这会导致泛型方法不能像你期望一样工作。根本原因就是泛型方法不知道类型参数代表的类型的特定的功能。当这种情况发生时,一个小的应用的动态可使运行时反射使问题得到解决。
小结:
这个原则很简单却很使用(trick),作者给的建议是:使用泛型参数类型对动态对象进行强制类型转换。
欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!
有关本书的其他章节翻译请[点击查看](/category/297763),转载请注明出处,尊重原创!
如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
转载请在**文首**注明出处:[http://dsqiu.iteye.com/blog/2090174](/blog/2090174)
更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)
- 第一章 C# 语言习惯
- 原则1:使用 属性(Poperty)代替可直接访问的数据成员(Data Member)
- 原则2:偏爱 readonly 而不是 const
- 原则3:选择 is 或 as 而不是强制类型转换
- 原则4:使用条件特性(conditional attribute)代替 #if
- 原则5:总是提供 ToString()
- 原则6:理解几个不同相等概念的关系
- 原则7:明白 GetHashCode() 的陷阱
- 原则8:优先考虑查询语法(query syntax)而不是循环结构
- 原则9:在你的 API 中避免转换操作
- 原则10:使用默认参数减少函数的重载
- 原则11:理解小函数的魅力
- 第二章 .NET 资源管理
- 原则12:选择变量初始化语法(initializer)而不是赋值语句
- 原则13:使用恰当的方式对静态成员进行初始化
- 原则14:减少重复的初始化逻辑
- 原则15:使用 using 和 try/finally 清理资源
- 原则16:避免创建不需要的对象
- 原则17:实现标准的 Dispose 模式
- 原则17:实现标准的 Dispose 模式
- 原则18:值类型和引用类型的区别
- 原则19:确保0是值类型的一个有效状态
- 原则20:更倾向于使用不可变原子值类型
- 第三章 用 C# 表达设计
- 原则21:限制你的类型的可见性
- 原则22:选择定义并实现接口,而不是基类
- 原则23:理解接口方法和虚函数的区别
- 原则24:使用委托来表达回调
- 原则25:实现通知的事件模式
- 原则26:避免返回类的内部对象的引用
- 原则27:总是使你的类型可序列化
- 原则28:创建大粒度的网络服务 APIs
- 原则29:让接口支持协变和逆变
- 第四章 和框架一起工作
- 原则30:选择重载而不是事件处理器
- 原则31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 实现排序关系
- 原则32:避免 ICloneable
- 原则33:只有基类更新处理才使用 new 修饰符
- 原则34:避免定义在基类的方法的重写
- 原则35:理解 PLINQ 并行算法的实现
- 原则36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原则37:构造并行算法的异常考量
- 第五章 杂项讨论
- 原则38:理解动态(Dynamic)的利与弊
- 原则39:使用动态对泛型类型参数的运行时类型的利用
- 原则40:使用动态接收匿名类型参数