当用作generics类型参数时,为什么“动态”不是所有类型的协变和逆变?

我想知道当用作generics类型参数时, dynamic是否在语义上等同于object 。 如果是这样,我很好奇为什么存在这种限制,因为在为变量或forms参数赋值时两者是不同的。

我在C#4.0中编写了一个小实验来梳理一些细节。 我定义了一些简单的接口和实现:

 interface ICovariance { T Method(); } interface IContravariance { void Method(T argument); } class Covariance : ICovariance { public T Method() { return default(T); } } class Contravariance : IContravariance { public void Method(T argument) { } } 

实验的有趣细节:

 class Variance { static void Example() { ICovariance c1 = new Covariance(); IContravariance c2 = new Contravariance(); ICovariance c3 = new Covariance(); IContravariance c4 = new Contravariance(); ICovariance c5 = new Covariance(); IContravariance c6 = new Contravariance(); // The following statements do not compile. //ICovariance c7 = new Covariance(); //IContravariance c8 = new Contravariance(); // However, these do. string s = new Covariance().Method(); new Contravariance().Method((dynamic)s); } } 

c1c2的前两个陈述表明基本的协方差和逆变是有效的。 然后我使用c3c4来表明dynamic可以以相同的方式用作generics类型参数。

c5c6的语句表明从dynamicobject的转换始终有效。 这并不太令人惊讶,因为object是所有其他类型的祖先。

c7c8的最终实验是我开始感到困惑的地方。 这意味着返回dynamic对象的方法不能替代返回string对象的方法,类似地,接受string对象的方法也不能采用dynamic对象。 分配和方法调用的最后两个语句显示情况显然不是这样,因此我的困惑。

我想到了这一点,并想知道这是否是为了防止程序员使用ICovariance作为类型转换之间的跳板,这会导致运行时错误,例如:

 ICovariance c9 = new Covariance(); ICovariance c10 = c9; // While this is definitely not allowed: ICovariance c11 = new Covariance(); 

然而,这在dynamic的情况下是不可信的,因为我们无论如何都会失去类型安全性:

 dynamic v1 = new Exception(); string v2 = v1; 

换句话说,问题是“为什么dynamic的语义在赋值和协方差/与generics的逆变之间是不同的?”

我想知道当用作generics类型参数时,动态是否在语义上等同于对象。

你的猜想是完全正确的。

“动态”作为一种类型只不过是带有滑稽帽子的“对象”,帽子上写着“而不是对类型对象的这个表达式进行静态类型检查,生成在运行时进行类型检查的代码”。 在所有其他方面,动态只是对象,故事的结尾。

我很好奇为什么存在这种限制,因为在为变量或forms参数赋值时两者是不同的。

从编译器的角度考虑它,然后从ILvalidation器的角度考虑。

当您为变量赋值时,编译器基本上会说“我需要生成从这样的类型和类型的值到变量的确切类型进行隐式转换的代码”。 编译器生成执行该操作的代码,ILvalidation程序validation其正确性。

也就是说,编译器生成:

 Frob x = (Frob)whatever; 

但是将转化限制为隐式转化,而不是显式转化。

当值是动态的时,编译器基本上会说“我需要生成在运行时询问此对象的代码,确定其类型,再次启动编译器,并吐出一小块IL,将此对象转换为该类型这个变量运行该代码,并将结果分配给这个变量。如果其中任何一个失败,抛出。“

也就是说,编译器生成道德等价物:

 Frob x = MakeMeAConversionFunctionAtRuntime((object)whatever); 

validation者甚至不会眨眼。 validation者看到一个返回Frob的方法。 如果它无法将“任何”变成Frob,那么该方法可能会抛出exception; 无论哪种方式,只有一个Frob被写入x。

现在想想你的协方差情况。 从CLR的角度来看,没有“动态”这样的东西。 你有一个“动态”类型参数的地方,编译器只是生成“对象”作为类型参数。 “dynamic”是C#语言function,而不是公共语言运行时function。 如果“对象”的协方差或逆变是不合法的,那么它对“动态”也是不合法的。 编译器无法生成IL以使CLR的类型系统以不同方式工作。

这就解释了为什么你观察到从ListList的转换,例如List List ; 编译器知道它们是相同的类型。 该规范实际上调用了这两种类型之间的身份转换; 它们是相同的类型。

这一切都有意义吗? 你似乎对那些充满活力的设计原则非常感兴趣; 而不是试图从自己的第一原则和实验中推断出它们,你可以省去自己的麻烦,阅读Chris Burrows关于这个主题的博客文章 。 他完成了大部分实现和相当多的function设计。

对于这一个:

 ICovariance c7 = new Covariance(); 

原因很明显,如果有可能那么你可以这样做:

 c7.Method().IndexOf(...); 

并且它肯定会失败,除非dynamic不是string或具有那些方法。

因为(即使在所有的变化之后)c#也不是动态语言。 只有在绝对安全的情况下才允许协方差。 您当然可以在dynamic变量上调用IndexOf ,但不能让API的用户无意中执行此操作。 例如,如果你返回这样的ICovariancedynamic卧底调用代码可能会失败!

记住规则,如果有从DB的演员表,则DB协变。 在这种情况下,没有从dynamicstring

但是dynamicobject协变,因为一切都是从它衍生出来的。

因为动态和协变/逆变关键字是如此新颖?

我猜你有点回答了你自己的问题。 在赋值语句中放宽赋值类型 – 安全性,因为这是动态的工作方式; 它会使编译时类型检查短路,因此您可以使用EXPECT进行分配,使其不受编译器无关的对象的影响。

然而,通用的协方差/逆变是严格控制的; 如果不使用输入/输出关键字(也在C#4.0中与动态一起引入),您无法转换任何一种方式。 即使允许使用co / contravariance,通用参数也要求类型位于inheritance层次结构的同一分支中。 String不是动态的,动态不是字符串(虽然两者都是对象,动态可以指代可以作为字符串访问的内容),因此协方差/逆变检查中固有的generics类型检查失败,而OTOH ,明确告诉编译器忽略大多数涉及动态的非generics操作。

“为什么动态的语义在赋值和协方差/与generics的逆变之间是不同的?”

答案是,在使用generics时,您将从数据类型本身中抽象出来。 但是,它也意味着generics足够通用,所有类型都将共享相同的function。

因此,如果你有’ICovariance c9 = new Covariance();`动态和exception都没有相同的function(如基类型)。 更重要的是,编译器没有关于如何从动态转换为exception的线索(即使它们都从对象inheritance)。

如果在dynamicException (除了对象)之间存在显式的inheritance层次结构,那么这将是有点好的。

原因之一是因为你可以贬低,但不能贬低。 EG,如果exceptioninheritance自动态,那就好了。 如果动态inheritance自Exception,它将是一个向上的有点交易而且不会好,因为可能存在“动态's data is not present inexception”中's data is not present in情况。

.NET内置了这些显式类型转换,您可以在System.Convert对象中看到它们的运行情况。 但是,如果没有自定义代码,则不能轻易地在彼此之间隐式或显式地转换超级特定的类型。 这就是为什么多类型失败的原因之一(就像’ICovariance c9 = new Covariance();`)的情况一样。 这也是为了保护类型安全。