|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
中间码是基于一个虚拟机器。源代码是最高层的,理论上从源代码开始直接编译成本地码能提供最大优化的。而中间码只能是转译成本地码,效率上难免受到损耗。根据虚拟机器所设定的体系结构的特点,和本地机器的差异的多少。随VisualStudio2010CTP表态的C#4和VB10,固然在撑持言语新特征方面走了相称纷歧样的两条路:C#侧重增添前期绑定和与静态言语相容的多少特征,VB10侧重简化言语和进步笼统才能;可是二者都增添了一项功效:泛型范例的协变(covariant)和反变(contravariant)。很多人对其懂得大概仅限于增添的in/out关头字,而对其诸多特征有所不知。上面我们就对此举行一些具体的注释,匡助人人准确利用该特征。
背景常识:协变和反变
良多人大概不不克不及很好地舆解这些来自于物理和数学的名词。我们无需往懂得他们的数学界说,可是最少应当能分清协变和反变。实践上这个词来历于范例和范例之间的绑定。我们从数组入手下手了解。数组实在就是一种和详细范例之间产生绑定的范例。数组范例Int32[]就对应于Int32这个底本的范例。任何范例T都有其对应的数组范例T[]。那末我们的成绩就来了,假如两个范例T和U之间存在一种平安的隐式转换,那末对应的数组范例T[]和U[]之间是不是也存在这类转换呢?这就牵涉到了将底本范例上存在的范例转换映照到他们的数组范例上的才能,这类才能就称为“可变性(Variance)”。在.NET天下中,独一同意可变性的范例转换就是由承继干系带来的“子类援用->父类援用”转换。举个例子,就是String范例承继自Object范例,以是任何String的援用都能够平安地转换为Object援用。我们发明String[]数组范例的援用也承继了这类转换才能,它能够转换成Object[]数组范例的援用,数组这类与原始范例转换偏向不异的可变性就称作协变(covariant)。
因为数组不撑持反变性,我们没法用数组的例子来注释反变性,以是我们如今就来看看泛型接口和泛型托付的可变性。假定有如许两个范例:TSub是TParent的子类,明显TSub型援用是能够平安转换为TParent型援用的。假如一个泛型接口IFoo<T>,IFoo<TSub>能够转换为IFoo<TParent>的话,我们称这个历程为协变,并且说这个泛型接口撑持对T的协变。而假如一个泛型接口IBar<T>,IBar<TParent>能够转换为T<TSub>的话,我们称这个历程为反变(contravariant),并且说这个接口撑持对T的反变。因而很好了解,假如一个可变性和子类到父类转换的偏向一样,就称作协变;而假如和子类到父类的转换偏向相反,就叫反变性。你记着了吗?
.NET4.0引进的泛型协变、反变性
方才我们解说观点的时分已用了泛型接口的协变和反变,但在.NET4.0之前,不管C#仍是VB里都不撑持泛型的这类可变性。不外它们都撑持托付参数范例的协变和反变。因为托付参数范例的可变性了解起来笼统度较高,以是我们这里禁绝备会商。已完整可以了解这些观点的读者本人想必可以本人往了解托付参数范例的可变性。在.NET4.0之前为何不同意IFoo<T>举行协变或反变呢?由于对接口来说,T这个范例参数既能够用于办法参数,也能够用于办法前往值。假想如许的接口
InterfaceIFoo(OfT)
SubMethod1(ByValparamAsT)
FunctionMethod2()AsT
EndInterface
interfaceIFoo<T>
{
voidMethod1(Tparam);
TMethod2();
}
假如我们同意协变,从IFoo<TSub>到IFoo<TParent>转换,那末IFoo.Method1(TSub)就会酿成IFoo.Method1(TParent)。我们都晓得TParent是不克不及平安转换成TSub的,以是Method1这个办法就会变得不平安。一样,假如我们同意反变IFoo<TParent>到IFoo<TSub>,则TParentIFoo.Method2()办法就会酿成TSubIFoo.Method2(),底本前往的TParent援用一定可以转换成TSub的援用,Method2的挪用将是不平安的。有此可见,在没有分外机制的限定下,接口举行协变或反变都是范例不平安的。.NET4.0改善了甚么呢?它同意在范例参数的声明时增添一个分外的形貌,以断定这个范例参数的利用局限。我们看到,假如一个范例参数仅仅能用于函数的前往值,那末这个范例参数就对协变相容。而相反,一个范例参数假如仅能用于办法参数,那末这个范例参数就对反变相容。以下所示:
InterfaceICo(OfOutT)
FunctionMethod()AsT
EndInterface
InterfaceIContra(OfInT)
SubMethod(ByValparamAsT)
EndInterface
interfaceICo<outT>
{
TMethod();
}
interfaceIContra<inT>
{
voidMethod(Tparam);
}
能够看到C#4和VB10都供应了迥然不同的语法,用Out来形貌仅能作为前往值的范例参数,用In来形貌仅能作为办法参数的范例参数。一个接口能够带多个范例参数,这些参数能够既有In也有Out,因而我们不克不及复杂地说一个接口撑持协变仍是反变,只能说一个接口对某个详细的范例参数撑持协变或反变。好比如有IBar<inT1,outT2>如许的接口,则它对T1撑持反变而对T2撑持协变。举个例子来讲,IBar<object,string>可以转换成IBar<string,object>,这里既有协变又有反变。
在.NETFramework中,很多接口都仅仅将范例参数用于参数或前往值。为了利用便利,在.NETFramework4.0里这些接口将从头声明为同意协变或反变的版本。比方IComparable<T>就能够从头声明成IComparable<inT>,而IEnumerable<T>则能够从头声明为IEnumerable<outT>。不外某些接口IList<T>是不克不及声明为in或out的,因而也就没法撑持协变或反变。
上面提起几个泛型协变和反变简单疏忽的注重事项:
1.唯一泛型接口和泛型托付撑持对范例参数的可变性,泛型类或泛型办法是不撑持的。
2.值范例不介入协变或反变,IFoo<int>永久没法酿成IFoo<object>,不论有没有声明out。由于.NET泛型,每一个值范例会天生专属的关闭机关范例,与援用范例版本不兼容。
3.声明属性时要注重,可读写的属性会将范例同时用于参数和前往值。因而只要只读属性才同意利用out范例参数,只写属功能够利用in参数。
协变和反变的互相感化
这是一个相称风趣的话题,我们先来看一个例子:
InterfaceIFoo(OfInT)
EndInterface
InterfaceIBar(OfInT)
SubTest(ByValfooAsIFoo(OfT))"对吗?
EndInterface
interfaceIFoo<inT>
{
}
interfaceIBar<inT>
{
voidTest(IFoo<T>foo);//对吗?
}
你能看出上述代码有甚么成绩吗?我声了然inT,然后将他用于办法的参数了,统统一般。但出乎你料想的是,这段代码是没法编译经由过程的!反而是如许的代码经由过程了编译:
InterfaceIFoo(OfInT)
EndInterface
InterfaceIBar(OfOutT)
SubTest(ByValfooAsIFoo(OfT))
EndInterface
interfaceIFoo<inT>
{
}
interfaceIBar<outT>
{
voidTest(IFoo<T>foo);
}
甚么?明显是out参数,我们却要将其用于办法的参数才正当?初看起来切实其实会有一些惊异。我们必要费一些周折来了解这个成绩。如今我们思索IBar<string>,它应当可以协酿成IBar<object>,由于string是object的子类。因而IBar.Test(IFoo<string>)也就协酿成了IBar.Test(IFoo<object>)。当我们挪用这个协变前方法时,将会传进一个IFoo<object>作为参数。想想,这个办法是从IBar.Test(IFoo<string>)协变来的,以是参数IFoo<object>必需可以酿成IFoo<string>才干满意原函数的必要。这里对IFoo<object>的请求是它可以反变成IFoo<string>!而不是协变。也就是说,假如一个接口必要对T协变,那末这个接口一切办法的参数范例必需撑持对T的反变。同理我们也能够看出,假如接口要撑持对T反变,那末接口中办法的参数范例都必需撑持对T协变才行。这就是办法参数的协变-反变交换准绳。以是,我们其实不能复杂地说out参数只能用于前往值,它的确只能间接用于声明前往值范例,可是只需一个撑持反变的范例帮忙,out范例参数就也能够用于参数范例!换句话说,in参数除间接声明办法参数以外,也仅能借助撑持协变的范例才干用于办法参数,仅撑持对T反变的范例作为办法参数也是不同意的。要想深入了解这一观点,第一次看大概会有点绕,倡议有前提的情形下多举行一些实行。
方才提到了办法参数上协变和反变的互相影响。那末办法的前往值会不会有一样的成绩呢?我们看以下代码:
InterfaceIFooCo(OfOutT)
EndInterface
InterfaceIFooContra(OfInT)
EndInterface
InterfaceIBar(OfOutT1,InT2)
FunctionTest1()AsIFooCo(OfT1)
FunctionTest2()AsIFooContra(OfT2)
EndInterface
interfaceIFooCo<outT>
{
}
interfaceIFooContra<inT>
{
}
interfaceIBar<outT1,inT2>
{
IFooCo<T1>Test1();
IFooContra<T2>Test2();
}
我们看到和方才恰好相反,假如一个接口必要对T举行协变或反变,那末这个接口一切办法的前往值范例必需撑持对T一样偏向的协变或反变。这就是办法前往值的协变-反变分歧准绳。也就是说,即便in参数也能够用于办法的前往值范例,只需借助一个能够反变的范例作为桥梁便可。假如对这个历程还不是出格分明,倡议也是写一些代码来举行实行。至此我们发明协变和反变有很多风趣的特征,以致于在代码里in和out都不像他们字面意义那末好了解。当你看到in参数呈现在前往值范例,out参数呈现在参数范例时,万万别晕倒,用本文的常识便可破解个中奇妙。
总结
经由本文的解说,人人应当已开端懂得的协变和反变的寄义,可以分清协变、反变的历程。我们还会商了.NET4.0撑持泛型接口、托付的协变和反变的新功效和新语法。最初我们还套了论的协变、反变与函数参数、前往值的互相感化道理,和由此发生的奇奥写法。我但愿人人看了我的文章后,可以将这些常识用于泛型程序计划傍边,准确使用.NET4.0的新增功效。祝人人利用兴奋!
本文出自:http://www.ckuyun.com/Ninputer/archive/2008/11/22/generic_covariant.html
有理由相信是能提供更出色的性能。很多平台无法支持复杂的编译器,因此需要二次编译来减少本地编译器的复杂度。当然可能做不到net网页编程编译器那么简易。 |
|