【问题标题】:Cast vs 'as' operator revisited重新审视 Cast vs 'as' 运算符
【发布时间】:2011-05-23 15:48:10
【问题描述】:

我知道已经有几篇关于演员表和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 操作符可以编译而强制转换不行?

【问题讨论】:

  • 在我看来foo as Bar指定类型参数,而(Bar)foo 正在尝试对未指定类型T 进行强制转换。这就是第二个示例有效的原因。您已将类型参数限制为可转换类型。第一个示例无法编译,因为不能保证 foo 可以类型安全地转换为 Bar
  • 为什么 as 操作符可以编译而强制转换不行?
  • @Robert:我不明白你在说什么。那应该抛出一个InvalidCastException,或者as也不应该编译。
  • @robert:所以你是说 cast 运算符需要这样的保证,而 as 运算符不需要?为什么?只是编译器团队的任意设计决定?
  • 如果您指定类型T 作为要转换的东西,编译时无法保证您在运行时指定的实际类型将转换为Bar。泛型是编译时构造;类型安全检查在编译时完成。由于无法在编译时检查 (Bar)foo 的类型安全性,因此会发生编译错误。但是使用as 运算符指定类型;它将运行时类型限制为Bar。所以不会出现编译错误,因为编译器可以推断出Bar是可转换的。

标签: c# clr


【解决方案1】:

解决您的第一个问题:as 运算符不只是忽略用户定义的转换,尽管这是相关的。更相关的是,演员表操作员做了两件相互矛盾的事情。强制转换运算符意味着:

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

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

这些是相反的。巧妙的技巧,让操作员做相反的事情。

相比之下,as 运算符只有第一种意义。 as 仅进行 boxingunboxingrepresentation-preserving 转换。演员表可以完成所有这些以及额外的表示更改转换。例如,将 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 只做三件事。它进行装箱转换、拆箱转换和类型测试,我们可以轻松生成完成这三件事的代码。

【讨论】:

  • 恭喜获得 100K!顺便说一句,规范中的这个在哪里?我没找到。
  • @SLaks:谢谢!规范中说从类型参数类型转换为不相关类型的位是“如果不存在从 E 到 T 的显式转换,则会发生绑定时错误。”不存在从 T 到 Bar 的显式转换,因此强制转换是非法的。
  • 一个美丽的展览。考虑到“转换”的多种含义,我确实怀疑那里有一些隐藏的深度。感谢您的洞察力!
【解决方案2】:

我们是否应该怀疑演员是 在编译时全部或部分解决 time 和 as 运算符不是?

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

请注意,就编译器而言 担心,没有已知的 之间的连接(未知在 编译时)类型 T 和 Bar。

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

请注意,(Bar)(object)foo 确实有效,因为没有类型可以具有到 Object 的转换运算符 [因为它是所有事物的基类],并且已知从 object 到 Bar 的强制转换不必处理转换运算符.

【讨论】:

    【解决方案3】:

    这是类型安全的问题。
    任何T 都不能通过转换为Bar,但任何T 都可以“看到”asBar,因为即使没有从TBar 的转换,行为也已明确定义.

    【讨论】:

      【解决方案4】:

      第一个编译只是因为 as 关键字是这样定义的。如果无法施放,则返回null。它是安全的,因为 as 关键字本身不会导致任何运行时问题。您可能检查或未检查变量是否为 null 的事实是另一回事。

      as 视为 TryCast 方法。

      【讨论】:

        【解决方案5】:

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

        考虑这两个调用:

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

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

        在您的特定情况下,您说该类型已经是 Bar 类型,因此很明显编译器可以进行引用转换,因为它知道这是安全的,不需要进行或进行转换。

        现在,as 转换更具“探索性”,它不仅不考虑用户转换,而且明确允许强制转换没有意义这一事实,因此编译器会放任自流。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-28
          • 1970-01-01
          • 2012-02-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-16
          相关资源
          最近更新 更多