17

I have just posted an answer to this question but I'm not entirely convinced of my answer.There are two things I'm wondering, consider this code:

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

According to C# Specification 5.0, there are two different kinds of conversion of as 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

Since, this is invalid because of (Foo<T>)str

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

I thought it should be translated as:

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

But the spec says this only happens when the type of E is dynamic.

So my questions are:

  1. Is the compiler translating this expression to a code that is normally invalid?
  2. When the type of E is dynamic why first it casts E to object then T while the (T)E is completely valid?
4

1 回答 1

12

编译器是否将此表达式转换为通常无效的代码?

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

编译器实际上并没有as运算符转换为带有is. 它将发出对 的 IL 调用isinst,包括asis

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操作符保持不变。

当 E 的类型是动态的时,为什么首先将 E 转换为 object 然后 T 而 (T)E 完全有效?

这在规范的细则中有所描述:

如果 E 的编译时类型是动态的,则与强制转换运算符不同, as 运算符不是动态绑定的(第 7.2.2 节)。因此,这种情况下的展开是:

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

需要强制转换object才能使对象的使用成为as可能dynamicas编译时操作,而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
}

编辑:

在与 Managed Languages Team 的 Vladimir Reshetnikov 交谈后,他解释了从“as operator”到“cast operator”的表示的语义实际上试图传达什么:

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

于 2015-01-26T17:17:09.553 回答