为什么"动态"而不是作为一个泛型类型参数时使用协变和逆变对于所有类型的? [英] Why is "dynamic" not covariant and contravariant with respect to all types when used as a generic type parameter?

查看:136
本文介绍了为什么"动态"而不是作为一个泛型类型参数时使用协变和逆变对于所有类型的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道如果动态是语义上等同于对象作为泛型类型参数一起使用时。如果是这样,我很好奇,为什么存在这种限制,因为两个赋值给变量或者形式参数的时候是不同的。



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

 接口ICovariance<出T> {T法(); } 

接口IContravariance<在T> {void方法(T参数); }

级协方差< T> :ICovariance< T>
{
公共T法(){返回默认值(T); }
}

级逆变< T> :IContravariance< T>
{
的public void方法(T参数){}
}

实验的有趣的细节:

 类差异
{
静态无效的实例()
{
ICovariance<对象> C1 =新的协方差<串GT;();
IContravariance<串GT; C2 =新逆变<对象>();

ICovariance<动态> C3 =新的协方差<串GT;();
IContravariance<串GT; C4 =新逆变<动态>();

ICovariance<对象> C5 =新的协方差<动态>();
IContravariance<动态> C6 =新逆变<对象>();

//下面的语句不进行编译。
// ICovariance<串GT; C7 =新的协方差<动态>();
// IContravariance<动态> C8 =新逆变<串GT;();

//然而,这些事情。
字符串s =新的协方差<动态方式>()()方法;
新逆变<串GT;()方法((动态)S)。
}
}

前两个语句与 C1 C2 显示,基本协变和逆变正在工作。然后我用 C3 4 来表示动态 CAN作为以同样的方式一个泛型类型参数。



C5 C6 显示,从动态对象转换始终有效。这是不是真的太奇怪,因为对象为所有其他类型的祖先。



最后的实验 C7 C8 是我开始变得迷茫。这意味着,返回动态对象的方法不是替代返回字符串数的方法,同样的接受方法字符串对象不能采取动态的。最后两个语句的分配和方法调用展示这显然并非如此,因此我的混乱。



我想到这一点,并想知道如果这是阻止程序员使用 ICovariance<动态> 类型转换之间的敲门砖,将导致运行时错误,如:

  ICovariance<动态> C9 =新的协方差<异常和GT;(); 
ICovariance<串GT; C10 = C9;
//虽然这是绝对不允许的:
ICovariance<串GT; C11 =新的协方差<异常和GT;();



不过,这是在动态因为我们失去类型安全反正:

 动态V1 =新的异常(); 
串V2 = V1;



换句话说,问题是,为什么动态分配和协方差/逆变与仿制药之间有什么不同?


解决方案

我想知道如果动态在语义上等同为泛型类型参数使用时对象。




您猜想完全正确的。



动态作为一种无非是对象,多与滑稽的帽子,一顶帽子,说:而不是做静态类型检查此类型的对象表达,生成的代码,做的类型在运行时检查。在所有其他方面,动态只是反对,故事的结尾。




我很好奇,为什么存在这种限制,因为这两个分配时是不同的值变量或形式参数。




这是编译器的角度想一想,然后从IL验证的角度来看。



当你给一个变量赋值,编译器基本上说:我需要生成代码,不会从这样的价值,这种类型的确切类型的隐式转换变量。该编译器产生的代码,这样做,和IL验证验证其正确性



也就是说,编译器生成:

  FROB X =(FROB)什么; 



但限制转换到隐式转换,而不是显式转换。



当值是动态的,编译器基本上说:我需要生成在运行时询问这个目标代码,决定了它的类型,再次启动编译器,并吐出IL一小块可以转换什么这个目的是该变量的类型,运行该代码,并将结果指定给此变量,而如果任何的失败,抛出。



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

  FROB X = MakeMeAConversionFunctionAtRuntime<&FROB GT;((对象)等等); 



验证器甚至不闪烁在那。验证看到一个返回FROB的方法。如果它不能把什么变成了FROB这种方法可能会抛出异常;无论哪种方式,无非是有史以来FROB被写入的X.



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



这则解释了为什么它是你观察到有,例如从一个转换, 列表与LT;动态> 和从列表<对象> ;编译器知道它们是相同的类型。该规范实际上是调用了这两类具有的标识的它们之间的转换;他们的相同的类型。



这是否是行得通?你似乎在那些以动感的设计原则很感兴趣;而不是试图从基本原理和实验自己演绎它们,你可以保存自己的麻烦和阅读克里斯·伯罗斯关于这个问题的博客文章。他做了大部分的实施和功能设计的相当数量的。


I am wondering if dynamic is semantically equivalent to object when used as a generic type parameter. If so, I am curious why this limitation exists since the two are different when assigning values to variables or formal parameters.

I've written a small experiment in C# 4.0 to tease apart some of the details. I defined some simple interfaces and implementations:

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) { }
}

The interesting details of the experiment:

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);       
    }
}

The first two statements with c1 and c2 demonstrate that basic covariance and contravariance are working. I then use c3 and c4 to show that dynamic can be used as a generic type parameter in the same fashion.

The statements with c5 and c6 reveal that a conversion from dynamic to object is always valid. This isn't really too surprising, since object is an ancestor of all other types.

The final experiment with c7 and c8 is where I start to become confused. It implies that methods that return dynamic objects are not substitutes for methods that return string ones, and similarly that methods that accept string objects cannot take dynamic ones. The final two statements with the assignment and method call show this is clearly not the case, hence my confusion.

I thought about this a little, and wondered if this is to prevent programmers from using ICovariance<dynamic> as a stepping stone between type conversions that would result in run-time errors, such as:

ICovariance<dynamic> c9 = new Covariance<Exception>();
ICovariance<string> c10 = c9;
// While this is definitely not allowed:
ICovariance<string> c11 = new Covariance<Exception>();

However, this is unconvincing in the case of dynamic since we lose type-safety anyway:

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

Put another way, the question is "Why does the semantics of dynamic differ between assignment and covariance/contravariance with generics?"

解决方案

I am wondering if dynamic is semantically equivalent to object when used as a generic type parameter.

Your conjecture is completely correct.

"dynamic" as a type is nothing more than "object" with a funny hat on, a hat that says "rather than doing static type checking for this expression of type object, generate code that does the type checking at runtime". In all other respects, dynamic is just object, end of story.

I am curious why this limitation exists since the two are different when assigning values to variables or formal parameters.

Think about it from the compiler's perspective and then from the IL verifier's perspective.

When you're assigning a value to a variable, the compiler basically says "I need to generate code that does an implicit conversion from a value of such and such a type to the exact type of the variable". The compiler generates code that does that, and the IL verifier verifies its correctness.

That is, the compiler generates:

Frob x = (Frob)whatever;

But limits the conversions to implicit conversions, not explicit conversions.

When the value is dynamic, the compiler basically says "I need to generate code that interrogates this object at runtime, determines its type, starts up the compiler again, and spits out a small chunk of IL that converts whatever this object is to the type of this variable, runs that code, and assigns the result to this variable. And if any of that fails, throw."

That is, the compiler generates the moral equivalent of:

Frob x = MakeMeAConversionFunctionAtRuntime<Frob>((object)whatever);

The verifier doesn't even blink at that. The verifier sees a method that returns a Frob. That method might throw an exception if it is unable to turn "whatever" into a Frob; either way, nothing but a Frob ever gets written into x.

Now think about your covariance situation. From the CLR's perspective, there is no such thing as "dynamic". Everywhere that you have a type argument that is "dynamic", the compiler simply generates "object" as a type argument. "dynamic" is a C# language feature, not a Common Language Runtime feature. If covariance or contravariance on "object" isn't legal, then it isn't legal on "dynamic" either. There's no IL that the compiler can generate to make the CLR's type system work differently.

This then explains why it is that you observe that there is a conversion from, say, List<dynamic> to and from List<object>; the compiler knows that they are the same type. The specification actually calls out that these two types have an identity conversion between them; they are identical types.

Does that all make sense? You seem very interested in the design principles that underly dynamic; rather than trying to deduce them from first principles and experiments yourself, you could save yourself the bother and read Chris Burrows' blog articles on the subject. He did most of the implementation and a fair amount of the design of the feature.

这篇关于为什么&QUOT;动态&QUOT;而不是作为一个泛型类型参数时使用协变和逆变对于所有类型的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆