假设我有一个通用的方法:
T Foo(T x) {
return x;
}
到目前为止一切顺利。但是如果它是Hashtable,我想做一些特别的事情。 (我知道这是一个完全做作的例子。Foo()
也不是一个非常令人兴奋的方法。一起玩。)
if (typeof(T) == typeof(Hashtable)) {
var h = ((Hashtable)x); // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}
织补。但公平地说,我实际上无法判断这是否应该是合法的C#。那么,如果我尝试以不同的方式做什么呢?
if (typeof(T) == typeof(Hashtable)) {
var h = x as Hashtable; // works (and no, h isn't null)
}
这有点奇怪。根据MSDN,expression as Type
是(除了评估表达式两次)与expression is type ? (type)expression : (type)null
相同。
如果我尝试使用文档中的等效表达式会发生什么?
if (typeof(T) == typeof(Hashtable)) {
var h = (x is Hashtable ? (Hashtable)x : (Hashtable)null); // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}
我发现,投射和as
之间唯一记录的差异是“as
运算符仅执行引用转换和装箱转换”。也许我需要告诉它我正在使用引用类型?
T Foo(T x) where T : class {
var h = ((Hashtable)x); // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
return x;
}
发生了什么事?为什么as
工作正常,而转换甚至不能编译?演员是否有效,或者as
不起作用,或者在我发现的这些MSDN文档中不存在演员和as
之间是否存在其他语言差异?
答案 0 :(得分:17)
Ben的回答基本上击中了头部的钉子,但要稍微扩大一点:
这里的问题是人们有一种自然的期望,即如果在编译时给出类型,泛型方法将执行与等效非泛型方法相同的操作。在你的特定情况下,人们会期望如果T很短,那么(int)t
应该做正确的事 - 将短路变为int。并且(double)t
应该将短片变为双倍。如果T是字节,那么(int)t
应该将字节转换为int,(double)t
应该将字节转换为double ...现在也许你开始看到问题了。我们必须生成的通用代码基本上必须在运行时再次启动编译器并进行完整类型分析,然后动态生成代码以按预期进行转换。
这可能很贵;我们在C#4中添加了这个功能,如果这是你真正想要的,你可以将对象标记为“动态”类型,编译器的一个简化版本将在运行时再次启动并为你做转换逻辑
但那件昂贵的东西通常不是人们想要的东西。
“as”逻辑远比强制转换逻辑更简单,因为它不需要处理除装箱,拆箱和引用转换之外的任何转换。它不必处理用户定义的转换,它不必处理花哨的表示改变转换,如“字节到双”,将一字节数据结构转换为八字节数据结构,依此类推。
这就是为什么“as as”在通用代码中被允许但是强制转换不是。
所有这一切:你几乎肯定做错了。如果必须在通用代码中进行类型测试,则代码不是通用的。这是一个非常糟糕的代码味道。
答案 1 :(得分:7)
C#中的强制转换操作符可以:
as Hashtable
总是意味着第二个。
通过使用约束消除值类型,您已经淘汰了选项1,但它仍然不明确。
以下两种“最佳”方法都有效:
Hashtable h = x as Hashtable;
if (h != null) {
...
}
或
if (x is Hashtable) {
Hashtable h = (Hashtable)(object)x;
...
}
第一个只需要一个类型测试,所以它非常有效。并且JIT优化器识别第二个,并将其视为第一个(至少在处理非泛型类型时,我不确定这个特定情况。)
答案 2 :(得分:4)
“C#编译器只允许您隐式地将泛型类型参数转换为Object或约束指定的类型,如代码块5所示。这种隐式转换是类型安全的,因为在编译时发现任何不兼容性。”< / p>
请参阅有关泛型和强制转换的部分: http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx#csharp_generics_topic5