演员与'as'运营商重访

时间:2011-05-23 15:48:11

标签: c# clr

我知道有几个帖子已经涉及了强制转换和as运算符之间的区别。他们大多都重述了同样的事实:

  • as运算符不会抛出,但如果投射失败则返回null
  • 因此,as运算符仅适用于引用类型
  • as运算符不会使用用户定义的转换运算符

答案然后倾向于无休止地讨论如何使用或不使用其中一个或每个的利弊,甚至是他们的表现(我根本不感兴趣)。

但这里还有更多工作要做。考虑:

static void MyGenericMethod<T>(T foo)
{
    var myBar1 = foo as Bar;  // compiles
    var myBar2 = (Bar)foo;    // does not compile ('Cannot cast expression of
                              // type 'T' to type 'Bar')
}

请不要介意这个明显懊悔的例子是不是好的做法。我关注的是两者之间非常有趣的差异,因为演员不会编译而as会编译。我真的很想知道是否有人可以对此有所了解。

通常需要注意的是,as运算符忽略了用户定义的转换,但在上面的示例中,显然两者的能力更强。注意as与编译器有关,在类型T和Bar之间(编译时未知)类型之间没有已知的连接。演员阵容完全是“运行时”。我们是否应该怀疑演员表是在编译时全部或部分解决的,as运算符不是?

顺便说一下,添加一个类型约束毫不奇怪地修复了强制转换,因此:

static void MyGenericMethod<T>(T foo) where T : Bar
{
    var myBar1 = foo as Bar;  // compiles
    var myBar2 = (Bar)foo;    // now also compiles
}

为什么as运算符编译而演员不是?

5 个答案:

答案 0 :(得分:30)

解决您的第一个问题:as运算符不仅忽视用户定义的转换,而且还是相关的。更重要的是,演员操作员做了两件相互矛盾的事情。演员经营者意味着:

  1. 我知道编译时类型Foo的这个表达式实际上是运行时类型Bar的对象。编译器,我现在告诉你这个事实,以便你可以利用它。假设我是正确的,请生成代码;如果我不正确,那么你可以在运行时抛出异常。

  2. 我知道编译时类型Foo的这个表达式实际上是运行时类型Foo。有一种将Foo的部分或全部实例转换为Bar实例的标准方法。编译器,请生成这样的转换,如果在运行时发现转换的值不可转换,则在运行时抛出异常。

  3. 那些是对立面。巧妙的技巧,让操作员做出相反的事情。

    相比之下,as运算符只具有第一感。 as仅执行装箱拆箱表示保留转化。演员可以完成所有这些以及更改表示更改的转换。例如,将int转换为short会将表示形式从四字节整数更改为两字节整数。

    这就是为什么“原始”演员在无约束的仿制品上不合法的原因;因为编译器没有足够的信息来确定它是什么类型的转换:装箱,拆箱,表示保留或表示改变。用户的期望是通用代码中的强制转换具有更强类型代码中的强制转换的所有语义,并且我们无法有效地生成该代码。

    考虑:

    void M<T, U>(T t, out U u)
    {
        u = (U)t;
    }
    

    你希望它能起作用吗?我们生成的代码可以处理:

    M<object, string>(...); // explicit reference conversion
    M<string, object>(...); // implicit reference conversion
    M<int, short>(...); // explicit numeric conversion
    M<short, int>(...); // implicit numeric conversion
    M<int, object>(...); // boxing conversion
    M<object, int>(...); // unboxing conversion
    M<decimal?, int?>(...); // lifted conversion calling runtime helper method
    // and so on; I could give you literally hundreds of different cases.
    

    基本上我们必须为再次启动编译器的测试发出代码,对表达式进行全面分析,然后发出新代码。我们在C#4中实现了这个功能;它被称为“动态”,如果这是你想要的行为,你可以随意使用它。

    as我们没有这些问题,因为as只做了三件事。它可以进行装箱转换,拆箱转换和类型测试,我们可以轻松生成执行这三项操作的代码。

答案 1 :(得分:5)

  

我们是否应该怀疑演员是谁?   在编译时全部或部分解决   时间和运营商没有?

您在问题的开头给出了答案:“as运算符不会使用用户定义的转换运算符” - 同时,强制转换执行,这意味着它需要找到那些运算符(或他们缺席)在编译时。

  

请注意,就编译器而言   有关,没有人知道   之间的联系(未知在   编译时)输入T和Bar。

T类型 未知的事实意味着编译器无法知道它与Bar之间是否没有连接。

请注意(Bar)(object)foo确实有效,因为没有类型可以将转换运算符赋予Object [因为它是所有内容的基类],并且已知从对象到Bar的转换不需要处理转换运算符。

答案 2 :(得分:2)

这是类型安全的问题 任何T都无法转换为Bar,但任何T都可以“看到”as一个Bar,因为行为定义明确,即使有没有从T转换为Bar

答案 3 :(得分:1)

第一个编译只是因为as关键字的定义方式。如果无法强制转换,则返回null。它是安全的,因为as关键字本身不会导致任何运行时问题。您可能已经或可能没有检查变量是否为空的事实是另一回事。

as视为TryCast方法。

答案 4 :(得分:0)

编译器不知道如何生成适用于所有情况的代码。

考虑这两个电话:

MyGenericMethod(new Foo1());
MyGenericMethod(new Foo2());

现在假设Foo1包含可以将其转换为Bar实例的强制转换运算符,而Foo2则来自Bar。显然,所涉及的代码在很大程度上取决于您传入的实际T

在你的特定情况下,你说类型已经是Bar类型,所以显然编译器可以只进行引用转换,因为它知道这是安全的,没有进行转换或需要转换。

现在,as转换更具“探索性”,不仅不考虑用户转换,而且明确允许转换无意义,因此编译器允许转换。