"as"运算符的行为与普通转换不同,出现了意外情况

9

我在这个问题中提问。 这段代码不能编译("Cannot convert Generic<T> to T"),原因在这里解释了(即使我期望在运行时会发生InvalidCastException, 而不是编译时错误)。

class NonGeneric
{
}

class Generic<T> : NonGeneric
    where T : NonGeneric
{
    T DoSomething()
    {
        return (T)this; // ** Cannot convert...
    }
}

已接受的解决方案给出了以下解决方法:

T DoSomething()
{
    return this as T;
}

我的问题是:为什么? as运算符应该与强制转换操作符完全等效:
as运算符类似于强制转换操作。但是,如果无法进行转换,则as返回null而不是引发异常。
如果this as T应该等同于this is T?(T)this:(T)null,那么为什么as T有效而(T)this甚至无法编译?据我所知,强制转换可以在比as更广泛的情况下使用:
请注意,as运算符仅执行引用转换、可空转换和装箱转换。 as运算符不能执行其他转换,例如用户定义的转换,应使用强制转换表达式执行。
那么这是为什么?这是as运算符的文档功能吗?这是具有通用类型的编译器/语言限制吗?请注意,此代码可以编译成功:
return (T)((object)this);

这是因为编译器无法确定 T 是否为 dynamic(即使有 where 约束),所以它将始终生成这样的代码吗?


@KeithPayne 抱歉,错误信息不正确。已修复。 - Adriano Repetti
@Adriano,你能澄清一下你的问题吗?为什么?为什么什么? - Sergey Berezovskiy
@SergeyBerezovskiy 为什么强制转换无法编译,但是使用 as 可以(此外,请参见使用强制转换完成相同操作的最后一个代码示例)。 - Adriano Repetti
@TimSchmelter 是的,我找到了它(请参见我的最后一次编辑),但这让我感到惊讶。我想知道这是编译器问题还是其他我不知道的问题! - Adriano Repetti
2
@Adriano:相关链接:https://dev59.com/IGfWa4cB1Zd3GeqPdAyO#11866282 - Tim Schmelter
显示剩余4条评论
3个回答

5
根据msdn的解释: as操作符类似于强制转换操作。但是,如果转换不可行,as操作符返回null而不是引发异常。 该代码等同于以下表达式,只不过表达式变量只计算一次。
    expression is type ? (type)expression : (type)null

另一个区别在于:

请注意,as 操作符只执行引用转换、可空转换和装箱转换。as 操作符无法执行其他转换,例如用户定义的转换,应该使用强制转换表达式来执行。


不,那是我预期的,但它并不等同,因为(类型)表达式甚至无法编译。这就是我的问题所在... - Adriano Repetti
它无法编译,因为编译器知道强制转换将会失败(在编译时),但是在使用 as 的情况下,它有返回 null 的选项(如果强制转换失败),所以它可以成功编译。 - Pratik Singhal
不,类型转换可能在运行时失败。它不应该被拒绝(另请参见编辑中的另一种情况)。至少它应该拒绝类型转换和“as”运算符... - Adriano Repetti

5
在 C# 语言规范中有这样一段话 (强调是我的),
如果 E 的编译时类型不是 dynamic,则 E as T 操作产生的结果与 E is T ? (T)(E) : (T)null 相同,只是 E 只被计算一次。可以预期编译器将优化 E as T 以执行至多一个动态类型检查,而不是上述扩展所暗示的两个动态类型检查。
如果 E 的编译时类型为 dynamic,则与转换操作符不同,as 操作符未动态绑定 (§7.2.2)。因此,在这种情况下,它的扩展为:E is T ? (T)(object)(E) : (T)null。
这似乎是使用 as 或将 this 强制转换为 object 成功编译的原因。此外,
在形式为 E as T 的操作中,E 必须是表达式且 T 必须是引用类型、已知为引用类型的类型参数或可空类型。此外,以下至少其中之一必须为真,否则会出现编译时错误:
• 从 E 到 T 存在标识 (§6.1.1)、隐式可空 (§6.1.4)、隐式引用 (§6.1.6)、装箱 (§6.1.7)、显式可空 (§6.2.3)、显式引用 (§6.2.4) 或拆箱 (§6.2.5) 转换。
• E 或 T 的类型是一个开放类型。
• E 是 null 字面量。
这就是您的泛型类目前的情况。

在我的情况下,它不是动态的,但我想编译器并不会因此而抱怨!谢谢! - Adriano Repetti
1
中奖了!我相当确定MSDN文档没有包含这个小细节。 - dcastro
1
为什么有着对"动态(dynamic)"的强调?我不明白DLR在这里的作用。 - Ritch Melton
1
由于在这些情况下(表达式是dynamic或者我认为是generic的性质),as操作符将在将表达式转换为T之前首先将其转换为object。这解释了为什么使用as expression(T)((object)expression)是可允许的,而(T)expression却不行。 - rae1
@RitchMelton 是的,即使它不应该,因为这里我们只有泛型而没有动态类型,如果没有其他东西...我想这是编译器的问题... - Adriano Repetti
显示剩余3条评论

-5

C#中的'as'运算符执行以下操作:

  1. 如果给定变量不是给定类型或其基类型之一,则返回null。它不会引发任何异常
  2. 只能应用于引用类型变量。
  3. 'as'不执行任何转换(隐式/显式)

'as'运算符比任何强制转换略快(即使在没有无效转换的情况下,由于异常而严重降低强制转换性能)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接