当右侧操作数是泛型时,"as" 运算符如何翻译?

How is "as" operator translated when the right-side operand is generic?

我刚刚发布了一个 to 但我并不完全相信我的 answer.There 有两件事我想知道,请考虑以下代码:

class Foo<T>
{ 
    void SomeMethod()
    {
        string str = "foo";
        Foo<T> f = str as Foo<T>;
    }
}

根据C# Specification 5.0as operator有两种不同的转换。

If the compile-time type of E is not dynamic, the operation E as T produces the same result as

E is T ? (T)(E) : (T)null

If the compile-time type of E is dynamic, unlike the cast operator the as operator is not dynamically bound (§7.2.2). Therefore the expansion in this case is:

E is T ? (T)(object)(E) : (T)null

因为,由于 (Foo<T>)str

,这是无效的
str is Foo<T> ? (Foo<T>)str : (Foo<T>)null;

我觉得应该翻译成:

str is Foo<T> ? (Foo<T>)(object)str : (Foo<T>)null;

但是规范说只有当 E 的类型是 dynamic 时才会发生这种情况。

所以我的问题是:

  1. 编译器是否将此表达式转换为通常无效的代码?
  2. E 的类型是动态的时,为什么它首先将 E 转换为 object 然后 T(T)E 是完全有效的?

Is the compiler translating this expression to a code that is normally invalid?

盯着规范看了大约一个小时后,我开始说服自己这只是规范中被忽视的边缘情况。请注意,这只是 C# 语言编写者用 is 运算符的语义表达 as 运算符的一种方式。

编译器实际上as 运算符转换为带有is 的三元运算符。对于 asis:

,它将发出对 isinst 的 IL 调用
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

查看已编译的 DLL,as 运算符保持不变。

When the type of E is dynamic why first it casts E to object then T while the (T)E is completely valid?

规范的细则中对此进行了描述:

If the compile-time type of E is dynamic, unlike the cast operator the as operator is not dynamically bound (§7.2.2). Therefore the expansion in this case is:

E is T ? (T)(object)(E) : (T)null

需要转换为 object 才能使 asdynamic 对象一起使用。 as 是一个 编译时 操作,而 dynamic 对象仅在 运行 时间 绑定。

编译器实际上将 dynamic 类型的对象视为类型 object 开头:

class Foo<T> 
{
    public void SomeMethod() 
    {
        dynamic str = "foo";
        Foo<T> f = str as Foo<T>;
    }
}

str 实际上被视为 object 开头:

.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 operator" 到 "cast operator" 的表示的语义实际上试图表达什么:

I agree, there is some imprecise language in the spec too. It says 'as' operator is always applicable if an open type involved, but then describes its evaluation in terms of casts, that might be not valid in some cases. It should say that casts in the expansion do not represent normal C# cast operator but just represent conversions that are permitted in 'as' operators. I'll take a note to fix it. Thanks!