为什么类在泛型中使用时实现的接口与接口类型不兼容? [英] Why is a class implementing an interface not compatible with the interface type when used in generics?
问题描述
有人知道为什么在指定的行上有错误吗?
根据类型兼容性页面。
通配符是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 $ c
事实上,你试图分配
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 havevar 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
andList2
are the same object, we have now succeeded in putting an object of typeTClass1
intoList2
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 implementedWildcard
but that was notTStringWildcard2
. Then that same thing would have been added toACollection1
and all of a sudden you'd succeeded in adding toACollection1
something that was notTStringWildcard2
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屋!