为什么类在泛型中使用时实现的接口与接口类型不兼容? [英] Why is a class implementing an interface not compatible with the interface type when used in generics?

查看:157
本文介绍了为什么类在泛型中使用时实现的接口与接口类型不兼容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人知道为什么在指定的行上有错误吗?



根据类型兼容性页面。
通配符是T1,TStringWildcard2是T2。

同样根据这个


如果表达式的值落在T1的范围内且满足以下至少一个条件,则可以将T2类型的表达式分配给T1类型的变量:T1是IUnknown或IDispatch接口类型,并且T2是Variant或OleVariant。 (如果T1是IUnknown,则变体的类型代码必须是varEmpty,varUnknown或varDispatch;如果T1是IDispatch,则变体的类型代码必须是varEmpty,varUnknown或varDispatch;如果T1是IDispatch,则变体的类型代码必须是varEmpty,varUnknown或varDispatch。) >如果T1是IUnknown,而T2是OleVariant,则可以将T2类型的表达式分配给T1类型的变量 - 因此第二个错误行也应该编译。



ACollection2:= ACollection1; //理论上应该编译,但不是。
由于LWildcard:= TStringWildcard2.Create(编译,因为LWildcard被声明为通配符接口的变量,而TStringWildcard2实现通配符接口)。

<$ p $因此,ACollection2(TDictionary< string,Wildcard>):= ACollection1; (TDictionary< string,TStringWildcard2>)应该正确编译?

程序TestCollections;

{$ APPTYPE CONSOLE}
{$ R * .res}

使用
System.SysUtils,System.Generics.Collections;
类型
Wildcard =接口
['{941128E5-D87C-4E3E-98D8-CF45EE6FEC09}']
过程清除;
end;
通配符< T> =接口(通配符)
函数getMatch:T;
end;
TParent = class(TInterfacedObject)
end;
TStringWildcard1 = class(TParent,Wildcard< string>)
函数getMatch:string;
程序清除;
end;
TStringWildcard2 = class(TParent,Wildcard,Wildcard< string>)
函数getMatch:string;
程序清除;
end;

{TStringWildcard1}

程序TStringWildcard1.Clear;
begin
end;

函数TStringWildcard1.getMatch:string;
begin
结果:='字符串匹配1';
end;

{TStringWildcard2}

过程TStringWildcard2.Clear;
begin
end;

函数TStringWildcard2.getMatch:string;
begin
结果:='字符串匹配2';
end;

var
LWildcard:通配符;
ACollection1:TDictionary< string,TStringWildcard2>;
ACollection2,ACollection3:TDictionary< string,Wildcard>;
ACollection4:TDictionary< string,IUnknown>;
begin
LWildcard:= TStringWildCard2.Create; //< - 与Wildcard
兼容的TStringWildcard2 //因为TStringWildcard2与通配符兼容,所以
// ACollection1应该与ACollection2
兼容ACollection1:= TDictionary< string,TStringWildcard2> 。创建;
ACollection2:= ACollection1;
ACollection4:= TDictionary< string,OleVariant> .Create;
结束。


解决方案

这里没有编译器错误。编译器的行为如同设计。由于类型确实不兼容,分配失败。 Delphi通用类型是不变式

文档说:


如果基本类型相同(或者是通用类型的别名)并且类型参数相同,那么两个实例化的泛型被认为是可分配兼容的。


现在,让我们看看第一个失败的任务:

  var 
ACollection1:TDictionary< string,TStringWildcard2> ;;
ACollection2:TDictionary< string,Wildcard>;
....
ACollection2:= ACollection1;

这个失败是因为类型参数不相同。



而对于 ACollection4 我们有

  var 
ACollection4:TDictionary< string,IUnknown>;
....
ACollection4:= TDictionary< string,OleVariant> .Create;

同样,类型参数也不相同。



语言设计者选择使通用类型不变是非常好的理由。考虑下面的例子。

  type 
TClass1 = class(TObject)
end;
TClass2 = class(TClass1)
end;

var
List1:TList< TClass1>;
List2:TList< TClass2>;
....
List2:= TList< TClass2> .Create;
List1:= List2; //不编译,但让我们想象它
List1.Add(TClass1.Create);

由于 List1 List2 是同一个对象,我们现在已经成功地将一个类型的对象放入 List2

事实上,你试图分配

  ACollection2:= ACollection1; 

说明了这个问题。假设赋值是有效的,然后你添加到 ACollection2 实现了通配符但是不是 TStringWildcard2 。然后,同样的事情将被添加到 ACollection1 ,并且突然之间您成功添加到 ACollection1 某物那不是 TStringWildcard2 ,并且已经打破了类型系统。



在支持通用差异的语言中,需要进行运行时检查来阻止这种情况发生碰巧,只有在昨天,我们自己的Jon Skeet在这个话题上发表了博文:数组协变:不仅丑陋,而且缓慢。所以,要小心你想要的东西!






因此,Delphi设计师选择使Delphi的泛型类型不变。 / p>

Does anyone know why there's an error on the indicated lines?

Seems like a compiler bug according to the Type Compatibility page. Wildcard is T1, and TStringWildcard2 is T2.

Also according to this

An expression of type T2 can be assigned to a variable of type T1 if the value of the expression falls in the range of T1 and at least one of the following conditions is satisfied: T1 is the IUnknown or IDispatch interface type and T2 is Variant or OleVariant. (The variant's type code must be varEmpty, varUnknown, or varDispatch if T1 is IUnknown, and varEmpty or varDispatch if T1 is IDispatch.)

If T1 is IUnknown and T2 is OleVariant, an expression of type T2 can be assigned to a variable of type T1 - therefore the second error line should compile as well.

ACollection2 := ACollection1; // theoretically should compile, but doesn't. Since LWildcard := TStringWildcard2.Create (compiles, since LWildcard is declared as a variable of the Wildcard interface, and TStringWildcard2 implements the Wildcard interface).

As such, therefore, ACollection2 (TDictionary<string, Wildcard> ) := ACollection1; (TDictionary<string, TStringWildcard2>) should compile right?

program TestCollections;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, System.Generics.Collections;
type
  Wildcard = interface
  ['{941128E5-D87C-4E3E-98D8-CF45EE6FEC09}']
    procedure Clear;
  end;
  Wildcard<T> = interface(Wildcard)
    function  getMatch: T;
  end;
  TParent = class(TInterfacedObject)
  end;
  TStringWildcard1 = class(TParent, Wildcard<string>)
    function  getMatch: string;
    procedure Clear;
  end;
  TStringWildcard2 = class(TParent, Wildcard, Wildcard<string>)
    function  getMatch: string;
    procedure Clear;
  end;

{ TStringWildcard1 }

procedure TStringWildcard1.Clear;
begin
end;

function TStringWildcard1.getMatch: string;
begin
  Result := 'String match 1';
end;

{ TStringWildcard2 }

procedure TStringWildcard2.Clear;
begin
end;

function TStringWildcard2.getMatch: string;
begin
  Result := 'String match 2';
end;

var
  LWildcard: Wildcard;
  ACollection1: TDictionary<string, TStringWildcard2>;
  ACollection2, ACollection3: TDictionary<string, Wildcard>;
  ACollection4: TDictionary<string, IUnknown>;
begin
  LWildcard := TStringWildCard2.Create; // <-- TStringWildcard2 compatible with Wildcard
// Since TStringWildcard2 is compatible with Wildcard, therefore, 
// ACollection1 should be compatible with ACollection2
  ACollection1 := TDictionary<string, TStringWildcard2>.Create;
  ACollection2 := ACollection1;
  ACollection4 := TDictionary<string, OleVariant>.Create;
end.

解决方案

There is no compiler bug here. The compiler is behaving as designed. The assignments fail because the types are indeed not compatible. Delphi generic types are invariant.

The documentation says:

Two instantiated generics are considered assignment compatible if the base types are identical (or are aliases to a common type) and the type arguments are identical.

Now, let's look at the first assignment that fails:

var
  ACollection1: TDictionary<string, TStringWildcard2>;
  ACollection2: TDictionary<string, Wildcard>;
....
ACollection2 := ACollection1;

This fails because the type arguments are not identical.

And for ACollection4 we have

var
  ACollection4: TDictionary<string, IUnknown>;
....
ACollection4 := TDictionary<string, OleVariant>.Create;

Again, the type arguments are not identical.


There's a very good reason for the language designers choosing to make generic types invariant. Consider the following example.

type
  TClass1 = class(TObject)
  end;
  TClass2 = class(TClass1)
  end;

var
  List1: TList<TClass1>;
  List2: TList<TClass2>;
....
List2 := TList<TClass2>.Create;
List1 := List2; // does not compile, but let's imagine that it did
List1.Add(TClass1.Create);

Since List1 and List2 are the same object, we have now succeeded in putting an object of type TClass1 into List2 which breaks the type system.

In fact your attempt to assign

ACollection2 := ACollection1;

illustrates this very issue. Suppose that assignment was valid and then you added to ACollection2 something that implemented Wildcard but that was not TStringWildcard2. Then that same thing would have been added to ACollection1 and all of a sudden you'd succeeded in adding to ACollection1 something that was not TStringWildcard2 and you've broken the type system.

In languages that support generic variance there need to be runtime checks to stop this happening. As it happens, only yesterday our very own Jon Skeet blogged on this very topic: Array covariance: not just ugly, but slow too. So, be careful what you wish for!


Because of this the Delphi designers elected to make Delphi generic types invariant.

这篇关于为什么类在泛型中使用时实现的接口与接口类型不兼容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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