当作为泛型类型参数使用时,我想知道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;换句话说,问题是“为什么的语义在赋值和协方差/逆方差与泛型之间存在差异?”?
发布于 2011-02-04 00:37:22
我想知道,当用作泛型类型参数时,dynamic在语义上是否等同于对象。
你的猜想是完全正确的。
"dynamic“作为类型只不过是带着滑稽帽子的" object”,它写着“而不是对类型对象的表达式执行静态类型检查,而是生成在运行时进行类型检查的代码”。在所有其他方面,动态只是对象,故事的结尾。
我很好奇为什么存在这种限制,因为两者在为变量或形式参数赋值时是不同的。
从编译器的角度,然后从IL验证器的角度来考虑它。
当您将一个值赋值给一个变量时,编译器基本上会说“我需要生成代码,从这样和那样的值到变量的确切类型进行隐式转换”。编译器生成这样做的代码,IL验证器验证其正确性。
也就是说,编译器生成:
Frob x = (Frob)whatever;但将转换限制为隐式转换,而不是显式转换。
当值是动态的时,编译器基本上会说:“我需要生成代码,在运行时询问这个对象,确定它的类型,再次启动编译器,然后释放出一小块IL,它将这个对象转换成这个变量的类型,运行该代码,并将结果分配给这个变量。如果其中任何一个失败,就抛出。”
也就是说,编译器生成的道德等效于:
Frob x = MakeMeAConversionFunctionAtRuntime<Frob>((object)whatever);验证者甚至都不眨眼。验证器看到一个返回Frob的方法。如果无法将“任何东西”转换为Frob,则该方法可能会引发异常;无论如何,除了Frob之外,任何东西都不会被写入x。
现在想想你的协方差情况。从CLR的角度来看,没有“动态”这样的东西。只要有一个类型参数是“动态的”,编译器就会生成"object“作为类型参数。"dynamic“是C#语言特性,而不是公共语言运行时特性。如果“对象”上的协方差或反方差不合法,那么它在“动态”上也是不合法的。编译器无法生成IL来使CLR的类型系统以不同的方式工作。
这就解释了为什么您观察到从(比方说) List<dynamic>到和从List<object>的转换;编译器知道它们是相同的类型。实际上,规范要求这两种类型之间进行身份转换;它们是相同的类型。
这一切都有意义吗?您似乎对动态的设计原则非常感兴趣;与其尝试从最初的原则和实验中推断出它们,您还可以省去麻烦,阅读克里斯·布伦斯关于这个问题的博客文章。他完成了大部分的实现和相当多的功能设计。
发布于 2011-02-03 22:59:16
这一次:
ICovariance<string> c7 = new Covariance<dynamic>();理由很明显,如果有可能的话,你可以:
c7.Method().IndexOf(...);而且它肯定会失败,除非dynamic不是string或者有这些方法。
因为(即使经过所有的更改) c#不是动态语言。只有在绝对安全的情况下才允许协方差。当然,您可以直接调用IndexOf变量,但是您不能让您的API用户无意中这样做。例如,如果您返回这样一个带有ICovariance<string>的dynamic,秘密调用代码可能会失败!
记住,如果有从D到B的强制转换,那么B是协变的。在本例中,不存在从dynamic到string的强制转换。
但是,dynamic是object的协变体,因为所有东西都是从它派生出来的。
发布于 2011-02-03 23:09:27
因为动态和协变/反变关键字都是新的?
我猜你回答了你自己的问题。赋值类型安全在赋值语句中是放松的,因为这是动态工作的方式;它是短路编译时类型检查,这样您就可以从编译器不知道的对象中进行分配。
但是,泛型协方差/逆方差是严格控制的;如果不使用in/out关键字(在C# 4.0中与dynamic一起引入),您就无法以任何一种方式进行转换。泛型参数,即使允许协/反向方差,也要求类型位于继承层次结构的同一分支中。字符串不是动态的,动态也不是字符串(虽然两者都是对象,动态可能引用可以作为字符串访问的内容),因此协方差/反向方差检查中固有的泛型类型检查失败,而OTOH则明确要求编译器忽略大多数涉及动态的非泛型操作。
https://stackoverflow.com/questions/4892704
复制相似问题