[英]Why is “dynamic” not covariant and contravariant with respect to all types when used as a generic type parameter?
我想知道当用作泛型类型参数时, 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) { }
}
实验的有趣细节:
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);
}
}
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>();
然而,这在dynamic
的情况下是不可信的,因为我们无论如何都失去了类型安全性:
dynamic v1 = new Exception();
string v2 = v1;
换句话说,问题是“为什么dynamic
的语义在赋值和协方差/与泛型的逆变之间是不同的?”
我想知道当用作泛型类型参数时,dynamic在语义上是否等同于object。
你的猜想是完全正确的。
“动态”作为一种类型只不过是带有滑稽帽子的“对象”,帽子上写着“而不是对类型对象的这个表达式进行静态类型检查,生成在运行时进行类型检查的代码”。 在所有其他方面,动态只是对象,故事的结尾。
我很好奇为什么存在这种限制,因为在为变量或形式参数赋值时两者是不同的。
从编译器的角度考虑它,然后从IL验证器的角度考虑。
当您为变量赋值时,编译器基本上会说“我需要生成一个代码,该代码执行从这样的类型和类型到变量的确切类型的隐式转换”。 编译器生成执行该操作的代码,IL验证程序验证其正确性。
也就是说,编译器生成:
Frob x = (Frob)whatever;
但是将转化限制为隐式转化,而不是显式转化。
当值是动态的时,编译器基本上会说“我需要生成在运行时询问此对象的代码,确定其类型,再次启动编译器,并吐出一小块IL,将此对象转换为该类型该变量运行该代码,并将结果分配给此变量。如果其中任何一个失败,抛出。“
也就是说,编译器生成道德等价物:
Frob x = MakeMeAConversionFunctionAtRuntime<Frob>((object)whatever);
验证者甚至不会眨眼。 验证者看到一个返回Frob的方法。 如果它无法将“无论”变成Frob,那么该方法可能会抛出异常; 无论哪种方式,只有一个Frob永远写入x。
现在想想你的协方差情况。 从CLR的角度来看,没有“动态”这样的东西。 你有一个“动态”类型参数的地方,编译器只是生成“对象”作为类型参数。 “dynamic”是C#语言功能,而不是公共语言运行时功能。 如果“对象”的协方差或逆变是不合法的,那么它对“动态”也是不合法的。 编译器无法生成IL以使CLR的类型系统以不同方式工作。
这就解释了为什么你观察到从List<dynamic>
到List<object>
的转换,例如List<dynamic>
List<object>
; 编译器知道它们是相同的类型。 规范实际上调用了这两种类型之间的身份转换; 它们是相同的类型。
这一切都有意义吗? 你似乎对充满活力的设计原则非常感兴趣; 而不是试图从自己的第一原则和实验中推断出它们,你可以省去自己的麻烦,阅读Chris Burrows关于这个主题的博客文章 。 他完成了大部分实现和相当多的功能设计。
对于这一个:
ICovariance<string> c7 = new Covariance<dynamic>();
原因很明显,如果有可能那么你可以这样做:
c7.Method().IndexOf(...);
它肯定会失败,除非dynamic
不是string
或具有那些方法。
因为(即使在所有的变化之后)c#也不是动态语言。 只有在绝对安全的情况下才允许协方差。 您当然可以在dynamic
变量上点击并调用IndexOf
,但是您无法让API的用户无意中执行此操作。 例如,如果你返回这样的ICovariance<string>
, dynamic
卧底调用代码可能会失败!
记住规则, D
是协变到B
,如果有从铸造D
到B
。 在这种情况下,没有从dynamic
到string
。
但是dynamic
是object
协变,因为一切都是从它衍生出来的。
因为动态和协变/逆变关键字是如此新颖?
我猜你有点回答你自己的问题。 在赋值语句中放宽赋值类型 - 安全性,因为这是动态的工作方式; 它会使编译时类型检查短路,因此您可以根据编译器不知道的对象进行EXPECT分配。
然而,通用的协方差/逆变是严格控制的; 如果不使用输入/输出关键字(也在C#4.0中与动态一起引入),您无法转换任何一种方式。 即使允许co / contravariance,通用参数也要求类型位于继承层次结构的同一分支中。 String不是动态的,动态不是字符串(虽然两者都是对象,动态可以指代可以作为字符串访问的内容),因此协方差/逆变检查中固有的泛型类型检查失败,而OTOH ,明确告诉编译器忽略大多数涉及动态的非泛型操作。
“为什么动态的语义在赋值和协方差/与泛型的逆变之间是不同的?”
答案是,在使用泛型时,您将从数据类型本身中抽象出来。 但是,它也意味着泛型足够通用,所有类型都将共享相同的功能。
因此,如果你有'ICovariance c9 = new Covariance();`动态和异常都没有相同的功能(如基类型)。 更重要的是,编译器没有关于如何从动态转换为异常的线索(即使它们都从对象继承)。
如果在dynamic
和Exception
(除了对象)之间存在显式的继承层次结构,那么这将是有点好的。
有些原因是因为你可以贬低,但不能贬低。 EG,如果异常继承自动态,那就好了。 如果动态继承自Exception,那将是一个upcast有点交易并且不会好,因为可能存在“动态's data is not present in
Exception中's data is not present in
情况。
.NET内置了这些显式类型转换,您可以在System.Convert
对象中看到它们的运行情况。 但是,如果没有自定义代码,则不能轻易地在彼此之间隐式或显式地转换超级特定的类型。 这就是为什么多类型失败的原因之一(就像'ICovariance c9 = new Covariance();`)的情况一样。 这也是为了保护类型安全。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.