为什么直接转换失败但“as”运算符在测试约束generics类型时成功?

“编译一些使用带有类型约束的generics的C#代码时,我遇到了一个有趣的好奇心。 我写了一个快速测试用例来说明。 我在Visual Studio 2010中使用.NET 4.0。

namespace TestCast { public class Fruit { } public class Apple : Fruit { } public static class Test { public static void TestFruit(FruitType fruit) where FruitType : Fruit { if (fruit is Apple) { Apple apple = (Apple)fruit; } } } } 

对Apple的强制转换失败并显示错误:“无法将类型’FruitType’转换为’TestCast.Apple’”。 但是,如果我更改行以使用as运算符,它将编译而不会出现错误:

 Apple apple = fruit as Apple; 

有人可以解释为什么会这样吗?

我在2015年10月使用此问题作为博客文章的基础。 谢谢你这个好问题!

有人可以解释为什么会这样吗?

“为什么”这些问题很难回答; 答案是“因为那是规范所说的”然后自然的问题是“为什么规范会这么说?”

所以让我让问题更加清晰:

哪些语言设计因素影响了使约束类型参数使给定的强制转换操作符非法的决定?

请考虑以下情形。 你有一个基本类型的水果,派生类型Apple和香蕉,现在是重要的部分,用户定义从Apple到Banana的转换。

当你被称为M时,你认为这应该怎么做?

 void M(T t) where T : Fruit { Banana b = (Banana)t; } 

阅读代码的大多数人会说这应该调用从Apple到Banana的用户定义转换。 但是C#generics不是C ++模板; 对于每个通用构造, 不会从头开始重新编译该方法。 相反,该方法被编译一次 ,并且在该编译期间,为每个可能的通用实例确定每个运算符(包括强制转换)的含义。

M的主体必须具有用户定义的转换。 M的身体会有身份转换。 M会出错。 在generics方法中,我们不能有三个不同的运算符含义,因此运算符被拒绝。

相反,你要做的是:

 void M(T t) where T : Fruit { Banana b = (Banana)(object)t; } 

现在两个转换都很清楚。 转换为对象是隐式引用转换; 转换为Banana是一种明确的参考转换。 永远不会调用用户定义的转换,如果这是用Cherry构造的,那么错误是在运行时,而不是编译时,因为它始终是对象转换时。

as运算符与cast运算符不同; 无论给出什么类型,它总是意味着相同的事情,因为as运算符不会调用用户定义的转换。 因此,它可以在演员非法的情况下使用。

“as运算符就像一个强制转换操作。但是,如果转换不可能,则返回null而不是引发exception。”

使用as运算符时不会收到编译时错误,因为编译器在使用as运算符时不会检查未定义的显式转换。 它的目的是允许尝试的有效或无效的运行时转换,如果不是,则返回null而不是抛出exception。

在任何情况下,如果您打算处理fruit不是Apple ,您应该将支票付诸实施

 var asApple = fruit as Appple; if(asApple == null) { //oh no } else { //yippie! } 

要回答这个问题,为什么编译器不会让你按照自己的意愿编写代码。 if在运行时被评估,因此编译器不知道只有在有效时才会发生强制转换。

为了让它工作,你可以在你的if中执行类似的操作:

 Apple apple = (Apple)(object)fruit; 

以下是关于同一问题的更多内容。

当然使用as运算符是最佳解决方案。

它在msdn doc中解释

as运算符就像一个强制转换操作。 但是,如果无法进行转换,则返回null而不是引发exception。 请考虑以下示例:

表达式类型除了表达式变量只计算一次之外,代码等效于以下表达式。

表达式是什么? (type)expression:(type)null请注意,as运算符仅执行引用转换,可空转换和装箱转换。 as运算符不能执行其他转换,例如用户定义的转换,而应使用转换表达式执行转换。

基类类型的变量可以保存派生类型。 要访问派生类型的方法,必须将值转换回派生类型。 使用as将阻止您获得InvalidCastException。 如果要处理特定的空引用方案,可以执行此操作。

 public class Fruit { public static explicit operator bool(Fruit D) { // handle null references return D.ToBoolean(); } protected virtual bool ToBoolean() { return false; } }