我刚刚向answer发布了this question,但我并不完全相信我的回答。我想知道有两件事情,请考虑以下代码:
class Foo<T>
{
void SomeMethod()
{
string str = "foo";
Foo<T> f = str as Foo<T>;
}
}
根据C# Specification 5.0
,as operator
有两种不同的转换方式。
如果
相同的结果E
的编译时类型不是dynamic
,则操作E as T
产生与E is T ? (T)(E) : (T)null
如果编译时类型
E
是dynamic
,则与强制转换运算符不同,as operator
不是动态绑定的(第7.2.2节)。因此,在这种情况下的扩展是: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
时才会发生。
所以我的问题是:
E
的类型是动态的,为什么首先它将E
投放到object
然后T
,而(T)E
完全有效?答案 0 :(得分:12)
编译器是否将此表达式转换为正常的代码 无效?
在盯着规范大约一个小时之后,我开始说服自己,这只是一个在规范中忽略的边缘情况。请注意,这仅仅是C#语言编写者用as
运算符的语义表达is
运算符的一种方式。
编译器实际上不会将as
运算符转换为具有is
的三元运算符。对于isinst
和as
,它会向is
发出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
运算符保持不变。
当E的类型是动态的时候,为什么首先它将E转换为对象然后是T. (T)E完全有效吗?
这在说明书的细则中有描述:
如果E的编译时类型是动态的,与强制转换运算符不同 as运算符不是动态绑定的(第7.2.2节)。因此 在这种情况下扩展是:
E is T ? (T)(object)(E) : (T)null
需要使用object
转换为as
对象dynamic
。 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
}
在与Managed Languages Team的Vladimir Reshetnikov交谈之后,他解释了从“as operator”到“cast operator”的表示的语义实际上是什么尝试:
我同意,规范中也有一些不精确的语言。它表示'as'运算符始终适用于涉及的开放类型,但随后描述了其在演员表方面的评估,在某些情况下可能无效。它应该说扩展中的强制转换不代表普通的C#强制转换运算符,而只是代表'as'运算符中允许的转换。我会记下来修复它。谢谢!