C# · 12月 28, 2021

c# – 为什么直接转换失败,但是在测试受限泛型类型时,“as”运算符成功?

“当编译一些使用类型约束的泛型的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>(FruitType fruit) where FruitType : Fruit { if (fruit is Apple) { Apple apple = (Apple)fruit; } } }}

对Apple的转换失败,并显示错误:“无法将”FruitType“类型转换为”TestCast.Apple“.但是,如果我更改行使用as操作符,它编译没有错误:

Apple apple = fruit as Apple;

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

解决方法 我用这个问题作为 a blog article in October 2015的基础.谢谢你的伟大的问题!

Could someone please explain why this is the case?

“为什么”问题难以回答?答案是“因为这是规范说的”,那么自然的问题就是“为什么说规范呢?

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

What language design factors influenced the decision to make the given cast operator illegal on constrained type parameters?

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

被称为M< Apple>?你认为这应该做什么?

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

大多数阅读代码的人都会说,这应该称为从苹果到香蕉的用户定义的转换.但C#泛型不是C模板;该方法对于每个通用构造都不会从头重新编译.相反,该方法被编译一次,并且在该编译期间,为每个可能的通用实例化确定每个运算符(包括转换)的含义.

M< Apple>的身体将必须有一个用户定义的转换. M香蕉的身体将有一个身份转换. M<樱桃>会是一个错误.我们在通用方法中不能具有运算符的三个不同含义,因此运算符被拒绝.

相反,你要做的是:

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

现在这两个转换都很清楚.转换为对象是一个隐式引用转换;转换为香蕉是一个明确的参考转换.用户定义的转换从不被调用,如果这是用Cherry构建的,则错误在运行时,而不是编译时,因为它始终是从对象转换.

作为运算符不像演员运算符;它总是意味着同样的事情,无论它给出什么类型,因为as操作符从不调用用户定义的转换.因此,它可以用于一个演员是非法的上下文.