繁体   English   中英

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

[英]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);       
    }
}

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

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

c7c8的最终实验是我开始感到困惑的地方。 这意味着,返回的方法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 ,如果有从铸造DB 在这种情况下,没有从dynamicstring

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

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

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

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

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

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

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

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

有些原因是因为你可以贬低,但不能贬低。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM