C# “是怎么回事?”;作为「;右侧操作数为泛型时转换的运算符? 我刚刚发布了一个ATO,但我不完全相信我的答案。有两件事我想知道,考虑一下这个代码: class Foo<T> { void SomeMethod() { string str = "foo"; Foo<T> f = str as Foo<T>; } }

C# “是怎么回事?”;作为「;右侧操作数为泛型时转换的运算符? 我刚刚发布了一个ATO,但我不完全相信我的答案。有两件事我想知道,考虑一下这个代码: class Foo<T> { void SomeMethod() { string str = "foo"; Foo<T> f = str as Foo<T>; } },c#,.net,dynamic,casting,as-operator,C#,.net,Dynamic,Casting,As Operator,如果E的编译时类型为dynamic,则与cast操作符不同,as操作符不受动态绑定(§7.2.2)。因此,这种情况下的扩展是: E is T ? (T)(object)(E) : (T)null 因为,这是无效的,因为(Foo)str str is Foo<T> ? (Foo<T>)str : (Foo<T>)null; str是Foo吗?(Foo)str:(Foo)null; 我想应该翻译成: str is Foo<T> ? (Foo<

如果
E
的编译时类型为
dynamic
,则与cast操作符不同,
as操作符不受动态绑定(§7.2.2)。因此,这种情况下的扩展是:

E is T ? (T)(object)(E) : (T)null
因为,这是无效的,因为
(Foo)str

str is Foo<T> ? (Foo<T>)str : (Foo<T>)null;
str是Foo吗?(Foo)str:(Foo)null;
我想应该翻译成:

str is Foo<T> ? (Foo<T>)(object)str : (Foo<T>)null;
str是Foo吗?(Foo)(object)str:(Foo)null; 但是规范说,只有当
E
的类型是
动态的时候才会发生这种情况

因此,我的问题是:

  • 编译器是否将此表达式转换为通常无效的代码
  • E
    的类型是动态的时,为什么首先将
    E
    强制转换为
    对象
    ,然后将
    T
    强制转换为
    (T)E
  • 编译器是否将此表达式转换为正常运行的代码 无效

    在看了大约一个小时的规范之后,我开始说服自己,这只是规范中忽略的一个边缘案例。请注意,这只是C语言编写者使用
    is
    运算符的语义将
    表示为
    运算符的一种方式

    编译器实际上不会将
    as
    运算符转换为具有
    is
    的三元运算符。它将发出对
    isinst
    的IL调用,用于
    as
    is

    IL_0000: nop
    IL_0001: ldstr "foo"
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: isinst class ConsoleApplication2.Foo`1<!T>
    IL_000d: stloc.1
    IL_000e: ret
    
    需要强制转换到
    对象
    ,以便在
    动态
    对象中尽可能地使用
    as
    是编译时操作,而
    动态
    对象仅在运行时绑定

    编译器实际上将
    动态
    类型对象视为类型
    对象
    ,首先:

    class Foo<T> 
    {
        public void SomeMethod() 
        {
            dynamic str = "foo";
            Foo<T> f = str as Foo<T>;
        }
    }
    
    .class private auto ansi beforefieldinit Foo`1<T>
        extends [mscorlib]System.Object
    {
        // Methods
        .method public hidebysig 
            instance void SomeMethod () cil managed 
        {
            // Method begins at RVA 0x2050
            // Code size 15 (0xf)
            .maxstack 1
            .locals init (
                [0] object,
                [1] class Foo`1<!T>
            )
    
            IL_0000: nop
            IL_0001: ldstr "foo"
            IL_0006: stloc.0
            IL_0007: ldloc.0
            IL_0008: isinst class Foo`1<!T>
            IL_000d: stloc.1
            IL_000e: ret
        } // end of method Foo`1::SomeMethod
    }
    
    编辑: 在与托管语言团队的弗拉基米尔·雷舍特尼科夫(Vladimir Reshetnikov)交谈后,他解释了从“as操作符”到“cast操作符”的表示语义实际上试图说服什么:

    我同意,规范中也有一些不精确的语言。它说,如果涉及开放类型,“as”运算符总是适用的,但随后用类型转换来描述其求值,这在某些情况下可能无效。应该说,扩展中的强制转换并不表示普通的C#cast运算符,而只是表示“as”运算符中允许的转换。我将注意修复它。谢谢


    我不能100%确定编译器的操作,但在没有任何动态的情况下,编译器知道
    E是否为T
    ,因此可能不会实际发出给定的代码,而是执行适当的部分。在它是动态的情况下,我假设它与出于某种原因强迫它被装箱有关,但我对动态表达式的行为了解不够,除了猜测之外。。。
    class Foo<T> 
    {
        public void SomeMethod() 
        {
            dynamic str = "foo";
            Foo<T> f = str as Foo<T>;
        }
    }
    
    .class private auto ansi beforefieldinit Foo`1<T>
        extends [mscorlib]System.Object
    {
        // Methods
        .method public hidebysig 
            instance void SomeMethod () cil managed 
        {
            // Method begins at RVA 0x2050
            // Code size 15 (0xf)
            .maxstack 1
            .locals init (
                [0] object,
                [1] class Foo`1<!T>
            )
    
            IL_0000: nop
            IL_0001: ldstr "foo"
            IL_0006: stloc.0
            IL_0007: ldloc.0
            IL_0008: isinst class Foo`1<!T>
            IL_000d: stloc.1
            IL_000e: ret
        } // end of method Foo`1::SomeMethod
    }