演员阵容与';作为';操作员再次访问

本文关键字:操作员 访问 作为 | 更新日期: 2023-09-27 17:57:32

我知道已经有几篇文章涉及强制转换和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运算符进行编译而不进行强制转换?

演员阵容与';作为';操作员再次访问

要解决您的第一个问题:as运算符不仅忽略了用户定义的转换,尽管这是相关的。更相关的是,cast操作符做了两件相互矛盾的事情。铸造操作员的意思是:

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

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

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

相比之下,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只做三件事。它执行装箱转换、取消装箱转换和类型测试,我们可以很容易地生成完成这三件事的代码。

我们应该怀疑演员阵容编译时全部或部分解决时间和作为操作员不是吗?

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

请注意,就编译器而言就目前而言之间的连接(未知,位于编译时)类型T和Bar。

T类型是未知的这一事实意味着编译器不能知道它和Bar之间是否没有连接。

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

这是一个类型安全问题
任何T都不能转换为Bar,但任何T都可以"看到"asBar,因为即使没有从TBar的转换,行为也得到了很好的定义。

第一个编译只是因为as关键字就是这样定义的。如果不能强制转换,它将返回null。它是安全的,因为as关键字本身不会引起任何运行时问题。事实上,你可能已经或可能没有检查变量为空是另一回事。

as看作一个TryCast方法。

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

考虑这两个调用:

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

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

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

现在,as转换更具"探索性",它不仅不考虑用户转换,而且明确允许强制转换没有意义,因此编译器对此不以为意。