12

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

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;

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

4

6 回答 6

23

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

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

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

所以让我把问题说得更清楚:

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

考虑以下场景。你有一个基本类型 Fruit,派生类型 Apple 和 Banana,现在重要的部分是用户定义的从 Apple 到 Banana 的转换。

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

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

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

的主体M<Apple>必须具有用户定义的转换。的主体M<Banana>将进行身份转换。 M<Cherry>将是一个错误。我们不能在泛型方法中对运算符具有三种不同的含义,因此该运算符被拒绝。

相反,您需要做的是:

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

现在两种转换都很清楚。到对象的转换是隐式引用转换;到 Banana 的转换是显式的引用转换。用户定义的转换永远不会被调用,如果这是用 Cherry 构造的,那么错误发生在运行时,而不是编译时,就像对象转换时一样。

as操作符不像 cast 操作符;无论给出什么类型,它总是意味着同样的事情,因为as操作员从不调用用户定义的转换。因此,它可以在强制转换为非法的情况下使用。

于 2013-10-03T18:37:41.727 回答
4

“as 运算符类似于强制转换操作。但是,如果无法进行转换,则 as 返回 null 而不是引发异常。”

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

在任何情况下,如果您打算处理fruitis not的情况Apple,您应该将您的检查执行为

var asApple = fruit as Appple;
if(asApple == null)
{
    //oh no
}
else
{
   //yippie!
}
于 2013-10-03T18:16:38.873 回答
2

回答为什么编译器不允许您按照自己的意愿编写代码的问题。if 在运行时被评估,所以编译器不知道强制转换只有在它有效的情况下才会发生。

为了让它工作,你“可以”在你的 if 中做这样的事情:

Apple apple = (Apple)(object)fruit;

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

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

于 2013-10-03T18:27:23.610 回答
0

它在 msdn doc 中有解释

as 运算符类似于强制转换操作。但是,如果无法进行转换,则 as 返回 null 而不是引发异常。考虑以下示例:

作为类型的表达式 代码等效于以下表达式,只是表达式变量只计算一次。

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

于 2013-10-03T18:17:02.807 回答
0

基类类型的变量可以保存派生类型。要访问派生类型的方法,必须将值转换回派生类型。使用 as 将防止您获得 InvalidCastException。如果你想处理特定的空引用场景,你可以这样做。

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

    protected virtual bool ToBoolean()
    {
         return false;
    }
}
于 2013-10-03T18:21:10.420 回答
-1

AS运算符关键字从 Visual Basic 继承其操作。

知情人士会告诉您,Visual Basic 是一种比 C# 更强大的语言,C# 本身就是在不断尝试制作一种类似 C 的语法语言,但具有 Visual Basic 的功能

这是因为 C 语法语言更受专业人士的欢迎,就像那些不标榜自己是 Basic 的语言一样。

于 2019-12-09T15:39:26.033 回答