演员与’as’运营商再次访问
我知道有几个post已经涉及演员和as
运算符之间的区别。 他们都大多重申相同的事实:
-
as
运算符不会抛出,但如果强制转换失败则返回null
- 因此,
as
运算符仅适用于引用类型 -
as
运算符不会使用用户定义的转换运算符
然后,答案倾向于无休止地讨论如何使用或不使用其中一个或每个的优点和缺点,甚至是他们的表现(我根本不感兴趣)。
但是这里还有更多工作要做。 考虑:
static void MyGenericMethod(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
运算符忽略了用户定义的转换,但在上面的例子中,显然两者的能力更强。 请注意,就编译器而言,类型T和Bar之间(编译时未知)之间没有已知的连接。 演员阵容完全是’运行时’。 我们是否应该怀疑演员阵容是在编译时全部或部分解决的,而运营商不是?
顺便说一下,添加类型约束不出所料地修复了强制转换,因此:
static void MyGenericMethod(T foo) where T : Bar { var myBar1 = foo as Bar; // compiles var myBar2 = (Bar)foo; // now also compiles }
为什么as
运算符编译和转换不?
解决您的第一个问题:不仅仅是as
运算符忽略了用户定义的转换,尽管这是相关的。 更重要的是,演员操作员做了两件相互矛盾的事情。 演员经营者意味着:
-
我知道编译时类型Foo的这个表达式实际上是运行时类型Bar的对象。 编译器,我现在告诉你这个事实,以便你可以利用它。 假设我是正确的,请生成代码; 如果我不正确,那么你可以在运行时抛出exception。
-
我知道编译时类型Foo的这个表达式实际上是运行时类型Foo。 有一种将Foo的部分或全部实例转换为Bar实例的标准方法。 编译器,请生成这样的转换,如果在运行时发现转换的值不可转换,则在运行时抛出exception。
那些是对立的 。 整洁的技巧,让一个操作员做相反的事情。
相比之下, as
运算符只具有第一感。 仅as
装箱 , 拆箱和保留表示的转换。 演员阵容可以完成所有这些以及其他表示更改转换。 例如,将int转换为short会将表示forms从四字节整数更改为两字节整数。
这就是为什么“原始”演员在无约束的仿制品上不合法的原因; 因为编译器没有足够的信息来确定它是什么类型的转换:装箱,拆箱,表示保留或表示改变。 用户的期望是通用代码中的强制转换具有更强类型代码的强制转换的所有语义,并且我们无法有效地生成该代码。
考虑:
void M(T t, out U u) { u = (U)t; }
你希望它能起作用吗? 我们生成哪些代码可以处理:
M
基本上我们必须为再次启动编译器的测试发出代码,对表达式进行全面分析,然后发出新代码。 我们在C#4中实现了这个function; 它被称为“动态”,如果这是你想要的行为,你可以随意使用它。
我们没有这些问题,因为只有三件事。 它可以进行装箱转换,拆箱转换和类型测试,我们可以轻松生成执行这三项操作的代码。
我们是否应该怀疑演员阵容是在编译时全部或部分解决的,而运营商不是?
你在问题的开头自己给出了答案:“as运算符不会使用用户定义的转换运算符” – 同时,强制执行 ,这意味着它需要在编译时找到那些运算符( 或它们的缺席 )。
请注意,就编译器而言,类型T和Bar之间(编译时未知)之间没有已知的连接。
T类型未知的事实意味着编译器无法知道它与Bar之间是否没有连接。
请注意, (Bar)(object)foo
确实有效,因为没有类型可以将对象的转换运算符[因为它是所有内容的基类],并且已知从对象到Bar的转换不必处理转换运营商。
这是类型安全的问题。
任何T
都不能转换为Bar
,但任何T
都可以“看到” as
Bar
因为即使没有从T
到Bar
转换,行为也很明确。
第一个编译只是因为as
关键字是如何定义的。 如果无法强制转换,则返回null
。 它的安全性因为as
关键字本身不会导致任何运行时问题。 您可能已经或可能没有检查变量为null的事实是另一回事。
可以将as
视为TryCast方法。
编译器不知道如何生成适用于所有情况的代码。
考虑这两个电话:
MyGenericMethod(new Foo1()); MyGenericMethod(new Foo2());
现在假设Foo1
包含一个可以将其转换为Bar
实例的Foo2
转换运算符,而Foo2
则从Bar
Foo2
而来。 显然,所涉及的代码在很大程度上取决于你传入的实际T
在您的特定情况下,您说该类型已经是Bar
类型,因此显然编译器可以只进行引用转换,因为它知道这是安全的,没有进行转换或需要转换。
现在, as
转换更具“探索性”,它不仅不考虑用户转换,而且明确允许转换无意义,因此编译器允许转换。