C# 为什么铸件会给出CS0030,而;作为「;作品

C# 为什么铸件会给出CS0030,而;作为「;作品,c#,generics,c#-4.0,casting,C#,Generics,C# 4.0,Casting,假设我有一个通用方法: T Foo(T x) { return x; } 到目前为止还不错。但是如果是哈希表,我想做一些特别的事情。(我知道这是一个完全人为的例子。Foo()也不是一个非常令人兴奋的方法。请配合。) 该死。不过,公平地说,我无法判断这是否合法。那么,如果我试着用另一种方式做呢 if (typeof(T) == typeof(Hashtable)) { var h = x as Hashtable; // works (and no, h isn't null)

假设我有一个通用方法:

T Foo(T x) {
    return x;
}
到目前为止还不错。但是如果是哈希表,我想做一些特别的事情。(我知道这是一个完全人为的例子。
Foo()
也不是一个非常令人兴奋的方法。请配合。)

该死。不过,公平地说,我无法判断这是否合法。那么,如果我试着用另一种方式做呢

if (typeof(T) == typeof(Hashtable)) {
    var h = x as Hashtable;  // works (and no, h isn't null)
}
这有点奇怪。根据MSDN,
表达式作为类型
是否与
表达式作为类型相同(除了对表达式求值两次之外)?(type)表达式:(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
工作得很好,而casting甚至无法编译?是该强制转换工作,还是该
as
不工作,还是该强制转换和
as
之间存在一些其他语言上的差异,而我在这些MSDN文档中找不到这些差异?

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

  • 装箱/拆箱
  • 上行/下行
  • 调用用户定义的转换运算符
作为Hashtable
总是指第二个

通过使用约束消除值类型,您删除了选项1,但它仍然不明确


以下是两种“最佳”方法,它们都有效:

Hashtable h = x as Hashtable;
if (h != null) {
    ...
}

第一个只需要一个类型测试,因此非常有效。JIT优化器会识别第二个,并像对待第一个一样对待它(至少在处理非泛型类型时,我不确定这个特殊情况)。

“C#编译器只允许您将泛型类型参数隐式转换为对象或约束指定的类型,如代码块5所示。这种隐式转换是类型安全的,因为任何不兼容都是在编译时发现的。”

请参阅有关泛型和铸造的部分:

本的回答基本上一针见血,但进一步说:

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

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

但这种昂贵的东西通常不是人们想要的

“as”逻辑远没有cast逻辑复杂,因为它不必处理装箱、拆箱和引用转换以外的任何转换。它不必处理用户定义的转换,也不必处理像“字节到双字节”这样的复杂表示转换将一字节数据结构转换为八字节数据结构,依此类推

这就是为什么在泛型代码中允许“as”,但强制转换不允许


所有这些都表明:您几乎肯定是做错了。如果您必须在泛型代码中进行类型测试,那么您的代码就不是泛型的。这是一种非常糟糕的代码味道。

我非常肯定您引用的MSDN语句在泛型之前就存在。不过,如果这就是所有的问题,那么肯定有一个有趣的问题!这是重复,还是仅仅是closely related:?我会说这是相关的——我是从另一个方向来的。如果是这样的话,这也不是我第一次尝试使用过期的MSDN文档了!@Ken:我认为另一个问题的标题是向后的,在我看来,这实际上是同一个问题。所以你是说铸造失败是因为可能有conv类型中的版本运算符?不是说你错了,而是..编译器已经知道泛型类型至少是
对象
(OP中的
T:class
部分),因此,如果你可以对
object
s进行盲转换,那么你是否也可以对泛型类型进行盲转换呢?这个解释对我来说很有意义。这也意味着MSDN文档没有错,只是写得很混乱:“A与B相同,除了案例C。请注意,它在方式D上也不同。”@Blindy:Downcast优先于用户定义的转换,当两者匹配时。由于每个对象都来自
对象
,因此
对象
的强制转换总是匹配Downcast。例如,如果将此代码编写为
(哈希表)(object)x;
,它将工作。“as”也可以装箱/取消装箱。“Somint as object”将装箱,并且“someObject as int?”如果是装箱的int,则会取消装箱,如果不是,则会给出一个null可为null的int。@EricLippert:只是编辑来处理这个问题。但它不会引入歧义,因为
不是动态的,编译器知道目标类型是引用类型还是值类型。(显然你已经知道了)要添加一个可能不是代码气味的示例,
Enumerable.Count
方法是通用的,但它会检查TSource是否是一个ICollection,通过调用.Count而不是迭代来获得巨大的性能优势。是的,我同意这不是代码气味,但可以有好的效果
Hashtable h = x as Hashtable;
if (h != null) {
    ...
}
if (x is Hashtable) {
    Hashtable h = (Hashtable)(object)x;
    ...
}