ide*_*ide 22 .net c# generics dynamic c#-4.0
我想知道在用作泛型类型参数时是否dynamic在语义上等效object.如果是这样,我很好奇为什么存在这种限制,因为在为变量或形式参数赋值时两者是不同的.
我在C#4.0中编写了一个小实验来梳理一些细节.我定义了一些简单的接口和实现:
interface ICovariance<out T> { T Method(); }
interface IContravariance<in T> { void Method(T argument); }
class Covariance<T> : ICovariance<T>
{
public T Method() { return default(T); }
}
class Contravariance<T> : IContravariance<T>
{
public void Method(T argument) { }
}
Run Code Online (Sandbox Code Playgroud)
实验的有趣细节:
class Variance
{
static void Example()
{
ICovariance<object> c1 = new Covariance<string>();
IContravariance<string> c2 = new Contravariance<object>();
ICovariance<dynamic> c3 = new Covariance<string>();
IContravariance<string> c4 = new Contravariance<dynamic>();
ICovariance<object> c5 = new Covariance<dynamic>();
IContravariance<dynamic> c6 = new Contravariance<object>();
// The following statements do not compile.
//ICovariance<string> c7 = new Covariance<dynamic>();
//IContravariance<dynamic> c8 = new Contravariance<string>();
// However, these do.
string s = new Covariance<dynamic>().Method();
new Contravariance<string>().Method((dynamic)s);
}
}
Run Code Online (Sandbox Code Playgroud)
前两个陈述c1并c2证明基本的协方差和逆变是有效的.然后我使用c3和c4显示dynamic可以以相同的方式用作泛型类型参数.
这些报表c5,并c6揭示了从转换dynamic到object一直有效.这并不太令人惊讶,因为它object是所有其他类型的祖先.
最后的实验c7和c8我开始变得困惑的地方.它意味着返回dynamic对象的方法不能替代返回对象的方法,string类似地,接受string对象的方法也不能取代对象dynamic.赋值和方法调用的最后两个语句显示情况显然不是这样,因此我的困惑.
我想到了这一点,并想知道这是否是为了防止程序员ICovariance<dynamic>在类型转换之间用作导致运行时错误的跳板,例如:
ICovariance<dynamic> c9 = new Covariance<Exception>();
ICovariance<string> c10 = c9;
// While this is definitely not allowed:
ICovariance<string> c11 = new Covariance<Exception>();
Run Code Online (Sandbox Code Playgroud)
然而,dynamic由于我们失去了类型安全,因此这是不能令人信服的:
dynamic v1 = new Exception();
string v2 = v1;
Run Code Online (Sandbox Code Playgroud)
换句话说,问题是"为什么赋值和协方差/与泛型相反的语义dynamic不同?"
Eri*_*ert 23
我想知道当用作泛型类型参数时,dynamic在语义上是否等同于object.
你的猜想是完全正确的.
"动态"作为一种类型只不过是带有滑稽帽子的"对象",帽子上写着"而不是对类型对象的这个表达式进行静态类型检查,生成在运行时进行类型检查的代码".在所有其他方面,动态只是对象,故事的结尾.
我很好奇为什么存在这种限制,因为在为变量或形式参数赋值时两者是不同的.
从编译器的角度考虑它,然后从IL验证器的角度考虑.
当您为变量赋值时,编译器基本上会说"我需要生成一个代码,该代码执行从这样的类型和类型到变量的确切类型的隐式转换".编译器生成执行该操作的代码,IL验证程序验证其正确性.
也就是说,编译器生成:
Frob x = (Frob)whatever;
Run Code Online (Sandbox Code Playgroud)
但是将转化限制为隐式转化,而不是显式转化.
当值是动态的时,编译器基本上会说"我需要生成在运行时询问此对象的代码,确定其类型,再次启动编译器,并吐出一小块IL,将此对象转换为该类型该变量运行该代码,并将结果分配给此变量.如果其中任何一个失败,抛出."
也就是说,编译器生成道德等价物:
Frob x = MakeMeAConversionFunctionAtRuntime<Frob>((object)whatever);
Run Code Online (Sandbox Code Playgroud)
验证者甚至不会眨眼.验证者看到一个返回Frob的方法.如果它无法将"无论"变成Frob,那么该方法可能会抛出异常; 无论哪种方式,只有一个Frob永远写入x.
现在想想你的协方差情况.从CLR的角度来看,没有"动态"这样的东西.你有一个"动态"类型参数的地方,编译器只是生成"对象"作为类型参数."dynamic"是C#语言功能,而不是公共语言运行时功能.如果"对象"的协方差或逆变是不合法的,那么它对"动态"也是不合法的.编译器无法生成IL以使CLR的类型系统以不同方式工作.
这就解释了为什么你观察到转换,比如说,List<dynamic>来自和转换List<object>; 编译器知道它们是相同的类型.规范实际上调用了这两种类型之间的身份转换; 它们是相同的类型.
这一切都有意义吗?你似乎对充满活力的设计原则非常感兴趣; 而不是试图从自己的第一原则和实验中推断出它们,你可以省去自己的麻烦,阅读Chris Burrows关于这个主题的博客文章.他完成了大部分实现和相当多的功能设计.