C# 铸造vs';as';接线员重访

C# 铸造vs';as';接线员重访,c#,clr,C#,Clr,我知道已经有几篇文章讨论了cast和as操作符之间的区别。它们大多都重申了相同的事实: 如果强制转换失败,as操作符将不会抛出,而是返回null 因此,as运算符仅适用于引用类型 as运算符将不使用用户定义的转换运算符 然后,答案往往会无休止地争论如何使用或不使用其中一种,以及每种方法的优缺点,甚至是它们的性能(我一点也不感兴趣) 但这里还有更多的东西在起作用。考虑: static void MyGenericMethod<T>(T foo) { var myBar1 =

我知道已经有几篇文章讨论了cast和
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
操作符编译而cast不编译

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

以这两个电话为例:

MyGenericMethod(new Foo1());
MyGenericMethod(new Foo2());
现在假设
Foo1
包含一个cast操作符,可以将其转换为
Bar
实例,而
Foo2
则从
Bar
下降。显然,所涉及的代码在很大程度上取决于您传入的实际
T

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


<>现在,<代码> AS >代码>转换更为“探索性”,它不仅不考虑用户的转换,它明确地允许了该语句是无意义的,所以编译器允许该幻灯片。

这是类型安全的问题。 任何
T
都不能转换为
Bar
,但任何
T
都可以被“视”为
a
Bar
,因为即使没有从
T
转换为
Bar
,行为也有很好的定义

我们是否应该怀疑演员是 全部或部分在编译时解决 时间和as操作员是否

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

请注意,就编译器而言 关于这一点,目前尚不清楚 服务器之间的连接(在上未知) 编译时)键入T和Bar

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


请注意,
(Bar)(object)foo
确实有效,因为任何类型都不能有到object的转换运算符[因为它是所有事物的基类],而且从object到Bar的转换都不需要处理转换运算符。

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


视为一种TryCast方法。

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

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

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

  • 这些是相反的。巧妙的技巧,有一个做相反事情的操作员

    相比之下,
    as
    运算符只有第一个意义。
    as
    只执行装箱、取消装箱和保留表示的转换。一个cast可以完成所有这些,再加上额外的表示形式更改转换。例如,将int转换为short会将表示形式从四字节整数更改为两字节整数

    这就是为什么“原始”类型转换在不受约束的泛型上是不合法的;因为编译器没有足够的信息来确定它是哪种类型的强制转换:装箱、取消装箱、保留表示或更改表示。用户的期望是,强制转换泛型代码具有强制转换强类型代码的所有语义,而我们无法高效地生成该代码

    考虑:

    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')
    }
    
    void M<T, U>(T t, out U u)
    {
        u = (U)t;
    }
    
    void M(T,out 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.
    
    M(…);//显式引用转换
    M(…);//隐式引用转换
    M(…);//显式数字转换
    M(…);//隐式数字转换
    M(…);//拳击转换
    M(…);//拆箱转换
    M(…);//调用运行时帮助程序方法的提升转换
    //等等;我可以给你上百个不同的箱子。
    
    基本上,我们必须为再次启动编译器的测试发出代码,对表达式进行完整分析,然后发出新代码。我们在C#4中实现了这个特性;它被称为“动态”,如果这是你想要的行为,你可以随意使用它


    对于
    as
    ,我们没有这些问题,因为
    as
    只做三件事。它进行装箱转换、取消装箱转换和类型测试,我们可以轻松生成完成这三项工作的代码。

    在我看来,它就像
    fooasbar