以下提出了投诉:
interface IInvariant<TInv> {}
interface ICovariant<out TCov> {
IInvariant<TCov> M(); // The covariant type parameter `TCov'
// must be invariantly valid on
// `ICovariant<TCov>.M()'
}
interface IContravariant<in TCon> {
void M(IInvariant<TCon> v); // The contravariant type parameter
// `TCon' must be invariantly valid
// on `IContravariant<TCon>.M()'
}
但我无法想象这不会是类型安全的。 (剪断*)这是不允许这样做的原因,还是有其他违反我不知道的类型安全的情况?
*我最初的想法确实令人费解,但尽管如此,答案非常彻底,@Theodoros Chatzigiannakis甚至以令人印象深刻的准确性解剖了我的初步假设。
除了回顾过去的好评之外,我意识到当ICovariant::M
被Func<IInvariant<Derived>>
分配给ICovariant<Derived>
时,我错误地认为ICovariant<Base>
的类型签名仍为M
。然后,将Func<IInvariant<Base>>
分配给ICovariant<Base>
将看起来来自ICovariant
,但当然是非法的。为什么不禁止最后这个显然是非法的演员? (所以我想)
我认为这种错误和切向猜测会减少这个问题,正如Eric Lippert也指出的那样,但出于历史目的,这个被剪切的部分:
对我来说最直观的解释是,以
TCov
为例,协变IInvariant<TCov> M()
意味着方法IInvariant<TSuper> M()
可以转换为某些TSuper super TCov
其中TInv
{1}},违反了IInvariant
中IInvariant
的不变性。但是,这种暗示似乎并不是必要的:TInv
M
的不变性很容易通过禁止{{1}}的演员来强制执行。
答案 0 :(得分:6)
让我们看一个更具体的例子。我们将对这些接口进行一些实现:
class InvariantImpl<T> : IInvariant<T>
{
}
class CovariantImpl<T> : ICovariant<T>
{
public IInvariant<T> M()
{
return new InvariantImpl<T>();
}
}
现在,让我们假设编译器没有抱怨这个并尝试以一种简单的方式使用它:
static IInvariant<object> Foo( ICovariant<object> o )
{
return o.M();
}
到目前为止一切顺利。 o
是ICovariant<object>
,该接口保证我们有一个可以返回IInvariant<object>
的方法。我们不必在这里进行演员表或转换,一切都很好。现在让我们调用方法:
var x = Foo( new CovariantImpl<string>() );
由于ICovariant
是协变的,因此这是一种有效的方法调用,我们可以用ICovariant<string>
代替ICovariant<object>
因为该协方差而需要Foo
。
但我们遇到了问题。在ICovariant<object>.M()
内,我们会调用IInvariant<object>
并希望它返回ICovariant
,因为这是ICovariant<string>
界面所说的内容。但是它无法做到这一点,因为我们通过的实际实现实际上实现了M
,而其IInvariant<string>
方法返回IInvariant<object>
,其中没有由于该接口的不变性而与 app.controller('resultCrtl',function($scope,myService,$http,$location,$window) {
/*guetting query from previous view*/
$scope.userquery=myService.get();
var uri;
/*testing the refresh */
if(!myService.get())
{
var loc=$location.search('query');
$window.alert(loc);
/* it returns an empty object*/
}
else {
$http({
method : 'GET',
url : '/response/' + $scope.userquery
}).success(function(data) {
$scope.result = data;
uri=encodeURI($scope.userquery);
$location.search('query',uri);
}).error(function(data, status, headers, config) {
alert( "failure");
});
$scope.search=function(){
/* same code to get data and update the url */
}
});
有关。它们是完全不同的类型。
答案 1 :(得分:5)
到目前为止,我不确定你的答案是否真的得到了答案。
为什么类类型参数的方差必须与其方法的方差相匹配?返回/参数类型参数?
它没有,所以问题是基于错误的前提。实际规则如下:
https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/
现在考虑一下:
interface IInvariant<TInv> {}
interface ICovariant<out TCov> {
IInvariant<TCov> M(); // Error
}
这是不允许这样做的原因,还是有其他一些违反我不知道的类型安全的情况?
我没有按照您的解释,所以我们只是说明为什么在不参考您的解释的情况下不允许这样做。在这里,让我用一些等价的类型替换这些类型。 IInvariant<TInv>
可以是T中不变的任何类型,让我们说ICage<TCage>
:
interface ICage<TAnimal> {
TAnimal Remove();
void Insert(TAnimal contents);
}
也许我们有Cage<TAnimal>
类型来实现ICage<TAnimal>
。
让我们用
替换ICovariant<T>
interface ICageFactory<out T> {
ICage<T> MakeCage();
}
让我们实现界面:
class TigerCageFactory : ICageFactory<Tiger>
{
public ICage<Tiger> MakeCage() { return new Cage<Tiger>(); }
}
一切都进展顺利。 ICageFactory
是协变的,所以这是合法的:
ICageFactory<Animal> animalCageFactory = new TigerCageFactory();
ICage<Animal> animalCage = animalCageFactory.MakeCage();
animalCage.Insert(new Fish());
我们只是把鱼放进虎笼里。那里的每一步都完全合法,我们最终违反了类型系统。我们得出的结论是,首先使ICageFactory
协变一定不合法。
让我们来看看你的逆变例子;它基本相同:
interface ICageFiller<in T> {
void Fill(ICage<T> cage);
}
class AnimalCageFiller : ICageFiller<Animal> {
public void Fill(ICage<Animal> cage)
{
cage.Insert(new Fish());
}
}
现在,界面是逆变的,所以这是合法的:
ICageFiller<Tiger> tigerCageFiller = new AnimalCageFiller();
tigerCageFiller.Fill(new Cage<Tiger>());
我们再一次将鱼放入虎笼中。我们再次得出结论,首先制造逆变型一定是非法的。
现在让我们考虑一下我们如何知道这些都是非法的问题。在第一种情况下,我们有
interface ICageFactory<out T> {
ICage<T> MakeCage();
}
相关规则是:
所有非void接口方法的返回类型必须是covariantly有效。
ICage<T>
&#34;是否有效并且#34;?
如果类型是: 1)指针类型,或非泛型类... NOPE 2)数组类型... NOPE 3)泛型类型参数类型... NOPE 4)构造的类,结构,枚举,接口或委托类型
X<T1, … Tk>
是的! ...如果第i个类型参数被声明为不变量,那么Ti必须是不变的有效。
TAnimal
中{p> ICage<TAnimal>
不变,因此T
中的ICage<T>
必须无限制有效。是吗?不能。为了无限制地有效,它必须是两者共同和不相容,但它只能共同有效。
因此这是一个错误。
对逆变情况进行分析是一项练习。
答案 2 :(得分:1)
为什么类类型参数的方差必须与其方法的方差相匹配?返回/参数类型参数?
它没有!
返回类型和参数类型不需要匹配封闭类型的方差。在您的示例中,对于两种封闭类型,它们都需要协变。这听起来违反直觉,但原因将在下面的解释中变得明显。
协变
TCov
意味着方法IInvariant<TCov> M()
可以转换为某些IInvariant<TSuper> M()
TSuper super TCov
,这违反了TInv
IInvariant
的不变性}}。但是,这种暗示似乎没有必要:IInvariant
TInv
的不变性很容易通过禁止M
的演员来强制执行。
例如,ICovariant<string>
有一个方法IInvariant<string> M()
。 &#34;不允许M
&#34;意味着当ICovariant<string>
分配给ICovariant<object>
时,它仍会保留带有签名IInvariant<string> M()
的方法。如果允许,那么这个完全有效的方法就会出现问题:
void Test(ICovariant<object> arg)
{
var obj = arg.M();
}
编译器应该针对obj
变量的类型推断出什么类型?它应该是IInvariant<string>
吗?为什么不IInvariant<Window>
或IInvariant<UTF8Encoding>
或IInvariant<TcpClient>
?所有这些都是有效的,请亲自看看:
Test(new CovariantImpl<string>());
Test(new CovariantImpl<Window>());
Test(new CovariantImpl<UTF8Encoding>());
Test(new CovariantImpl<TcpClient>());
显然,方法(M()
)的静态已知返回类型可能不依赖于实现的接口(ICovariant<>
)对象的运行时类型!
因此,当泛型类型被分配给具有更多通用类型参数的另一个泛型类型时,使用相应类型参数的成员签名也必须更改为更通用的类型。如果我们想要保持类型安全,那就没办法了。现在让我们看一下&#34;更通用的&#34;在每种情况下都意味着。
ICovariant<TCov>
需要IInvariant<TInv>
协变对于string
的类型参数,编译器&#34;看到&#34;这个具体类型:
interface ICovariant<string>
{
IInvariant<string> M();
}
并且(如上所述)对于object
的类型参数,编译器&#34;看到&#34;相反,这个具体类型:
interface ICovariant<object>
{
IInvariant<object> M();
}
假设实现前一个接口的类型:
class MyType : ICovariant<string>
{
public IInvariant<string> M()
{ /* ... */ }
}
请注意,此类型中M()
的实际实施仅涉及返回IInvariant<string>
,而不关心差异。牢记这一点!
现在通过设置ICovariant<TCov>
协变的类型参数,您声称ICovariant<string>
应该可以分配给ICovariant<object>
,如下所示:
ICovariant<string> original = new MyType();
ICovariant<object> covariant = original;
...而你也断言你现在可以这样做:
IInvariant<string> r1 = original.M();
IInvariant<object> r2 = covariant.M();
请记住,original.M()
和covariant.M()
是对同一方法的调用。实际的方法实现只知道它应该返回Invariant<string>
。
因此,在执行后一个调用期间的某个时刻,我们隐式地将IInvariant<string>
(由实际方法返回)转换为IInvariant<object>
(这是协变签名所承诺的)。为此,IInvariant<string>
必须可分配给IInvariant<object>
。
要概括起来,相同的关系必须适用于IInvariant<S>
和IInvariant<T>
S : T
。这正是协变类型参数的描述。
IContravariant<TCon>
也要求IInvariant<TInv>
协变对于object
的类型参数,编译器&#34;看到&#34;这个具体类型:
interface IContravariant<object>
{
void M(IInvariant<object> v);
}
对于string
的类型参数,编译器&#34;看到&#34;这个具体类型:
interface IContravariant<string>
{
void M(IInvariant<string> v);
}
假设实现前一个接口的类型:
class MyType : IContravariant<object>
{
public void M(IInvariant<object> v)
{ /* ... */ }
}
同样,注意到M()
的实际实现假设它会从您那里获得IInvariant<object>
并且它并不关心方差。
现在通过创建IContravariant<TCon>
的类型参数,您声称IContravariant<object>
应该可以像这样分配给IContravariant<string>
......
IContravariant<object> original = new MyType();
IContravariant<string> contravariant = original;
...而你也断言你现在可以这样做:
IInvariant<object> arg = Something();
original.M(arg);
IInvariant<string> arg2 = SomethingElse();
contravariant.M(arg2);
同样,original.M(arg)
和contravariant.M(arg2)
是对同一方法的调用。该方法的实际实现要求我们传递任何IInvariant<object>
。
因此,在执行后一个调用的某个时刻,我们隐式地将IInvariant<string>
(这是逆变签名期望从我们这里得到的)转换为IInvariant<object>
(这就是实际的方法预期)。为此,IInvariant<string>
必须可分配给IInvariant<object>
。
要概括起来,每个IInvariant<S>
都应该分配到IInvariant<T>
S : T
。所以我们再次查看协变类型参数。
现在你可能想知道为什么会出现不匹配。协方差和逆变的二元性在哪里?它仍然存在,但形式不太明显: