为什么铸造给CS0030,而"as"有效?

Ken*_*Ken 19 c# generics casting c#-4.0

假设我有一个通用的方法:

T Foo(T x) {
    return x;
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但是如果它是Hashtable,我想做一些特别的事情.(我知道这是一个完全做作的例子. Foo()也不是一个非常令人兴奋的方法.一起玩.)

if (typeof(T) == typeof(Hashtable)) {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
}
Run Code Online (Sandbox Code Playgroud)

该死.但公平地说,我实际上无法判断这是否应该是合法的C#.那么,如果我尝试以不同的方式做什么呢?

if (typeof(T) == typeof(Hashtable)) {
    var h = x as Hashtable;  // works (and no, h isn't null)
}
Run Code Online (Sandbox Code Playgroud)

这有点奇怪.根据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'
}
Run Code Online (Sandbox Code Playgroud)

铸造和as我看到的唯一记录的区别是" as操作员只执行参考转换和装箱转换".也许我需要告诉它我正在使用引用类型?

T Foo(T x) where T : class {
    var h = ((Hashtable)x);  // CS0030: Cannot convert type 'T' to 'System.Collections.Hashtable'
    return x;
}
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?为什么as工作正常,而铸造甚至不会编译?演员应该工作,还是应该as不工作,或者在演员之间是否存在其他语言差异as?我发现这些MSDN文档中没有?

Eri*_*ert 18

Ben的答案基本上都击中了头部,但要稍微扩大一点:

这里的问题是人们有一种自然的期望,即如果在编译时给出类型,泛型方法将执行与等效非泛型方法相同的操作.在你的特殊情况下,人们会期望如果T很短,那么(int)t应该做正确的事情 - 把空头变成一个int.并且(double)t应该把空头变成双倍.如果T是字节,那么(int)t应该将字节转换为int,并且(double)t应该将字节转换为double ...现在也许你开始看到问题了.我们必须生成的通用代码基本上必须在运行时再次启动编译器并进行完整类型分析,然后动态生成代码以按预期进行转换.

这可能很昂贵; 我们在C#4中添加了这个功能,如果这是你真正想要的,你可以将对象标记为"动态"类型,编译器的一个简化版本将在运行时再次启动并为你做转换逻辑.

但那件昂贵的东西通常不是人们想要的.

"as"逻辑不如转换逻辑那么复杂,因为除了装箱,拆箱和引用转换之外,它不需要处理任何转换.它不必处理用户定义的转换,它不必处理花哨的表示变化转换,如"字节到双精度",将单字节数据结构转换为8字节数据结构,依此类推.

这就是为什么"as"在通用代码中被允许但是强制转换不允许的原因.

所有这一切:你几乎肯定做错了.如果必须在通用代码中进行类型测试,则代码不是通用的.这是一个非常糟糕的代码味道.

  • 要添加一个可能不是代码气味的示例,`Enumerable.Count <TSource>`方法是通用的,但检查TSource是否是ICollection,通过调用.Count而不是迭代来获得巨大的,巨大的性能优势.是的,一个边缘案例,我同意它不是代码味道,但它有充分的理由. (3认同)
  • @MichaelStum:我明白你的观点.然而,要问一个对象*"你支持这个界面吗?"仍然是一个非常"通用"的事情.非泛型代码也可以做到这一点.但是当你说"如果T恰好是HashTable的类型那么......"那么T根本就不是以通用方式使用的. (2认同)

Ben*_*igt 7

C#中的强制转换操作符可以:

  • 盒/拆箱
  • 上溯造型/垂头丧气
  • 调用用户定义的转换运算符

as Hashtable 总是意味着第二个.

通过使用约束消除值类型,您已经淘汰了选项1,但它仍然是模棱两可的.


以下两种"最佳"方法都有效:

Hashtable h = x as Hashtable;
if (h != null) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

要么

if (x is Hashtable) {
    Hashtable h = (Hashtable)(object)x;
    ...
}
Run Code Online (Sandbox Code Playgroud)

第一个只需要一个类型测试,所以它非常有效.并且JIT优化器识别第二个,并将其视为第一个(至少在处理非泛型类型时,我不确定这个特定情况.)