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

时间:2013-10-03 18:08:26

标签: c# generics .net-4.0 casting constraints

``编译一些使用泛型和类型约束的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;

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

6 个答案:

答案 0 :(得分:20)

我用这个问题作为a blog article in October 2015的基础。谢谢你提出的好问题!

  

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

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

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

  

哪些语言设计因素影响了使约束类型参数使给定演员操作符合法的决定?

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

当您被称为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运算符与强制运算符不同;无论给出什么类型,它总是意味着相同的事情,因为as运算符不会调用用户定义的转换。因此,它可以在演员阵营非法的情况下使用。

答案 1 :(得分:4)

"The as operator is like a cast operation. However, if the conversion is not possible, as returns null instead of raising an exception."

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

在任何情况下,如果您计划处理fruit不是Apple的情况,您应该将支票视为

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

答案 2 :(得分:2)

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

为了让它工作,你可以“在if:

中做这样的事情。”
Apple apple = (Apple)(object)fruit;

Here's关于同一问题的更多内容。

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

答案 3 :(得分:0)

在msdn doc中解释

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

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

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

答案 4 :(得分: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;
    }
}

答案 5 :(得分:0)

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

那些知道的人会告诉您,Visual Basic是比C#更强大的语言,而C#本身正在不断尝试制作类似C的语法语言,但具有Visual Basic的功能

这是因为C语法语言在专业人士中更受欢迎,以及那些不会宣称自己是基本语言的语言。