2009-11-11 8 views
6

Grundsätzlich möchte ich eine Klasse in der Lage sein, zwei verschiedene Versionen der gleichen generischen Schnittstelle zu implementieren.Do Generics vermasseln Interface Name Mapping?

Betrachten Sie diesen Code

type 
    // a generic interface 
    ITest<T> = interface 
    ['{6901FE04-8FCC-4181-9E92-85B73264B5DA}'] 
    function Val: T; 
    end; 

    // a class that purports to implement two different types of that interface 
    TTest<T1, T2> = class(TInterfacedObject, ITest<T1>, ITest<T2>) 
    protected 
    fV1: T1; 
    fV2: T2; 
    public 
    constructor Create(aV1: T1; aV2: T2); 
    function Val: T1;    // Val() for ITest<T1> 
    function T2Val: T2;    // Val() for ITest<T2> 
    function ITest<T2>.Val = T2Val; // mapping 
    end; 

constructor TTest<T1, T2>.Create(aV1: T1; aV2: T2); 
begin 
    inherited Create; 
    fV1 := aV1; 
    fV2 := aV2; 
end; 

function TTest<T1, T2>.T2Val: T2; 
begin 
    result := fV2; 
end; 

function TTest<T1, T2>.Val: T1; 
begin 
    result := fV1; 
end; 

///////////// 
procedure Test; 
var 
    t : TTest<integer, string>; 
begin 
    t := TTest<integer, string>.Create(39, 'Blah'); 
    ShowMessage((t as ITest<string>).Val);   // this works as expected 
    ShowMessage(IntToStr((t as ITest<integer>).Val)); // this gets AV 
end; 

Die erste Showmessage zeigt 'Blah', wie ich erwarten würde, aber die zweite stürzt ab. Der Grund dafür ist, dass der Aufruf T2Val() anstelle von Val() aufruft, wie ich es erwartet hätte. Offensichtlich bildet die Konfliktauflösungszuordnung die Methode für beide Arten von Schnittstellen ab und nicht nur für ITest: T2.

Also, hier ist meine Frage (n).

Ist das ein Fehler? Womit, ich meine, wollte Embarcadero dies unterstützen und einfach falsch umsetzen? Oder hatten sie niemals die Absicht, Programmierern zu erlauben, so etwas überhaupt zu machen? (Ehrlich gesagt, war ich ein wenig überrascht, dass mein Testprogramm sogar kompiliert)

Wenn dies ein Fehler ist, hat jemand eine Idee, wenn es eine Problemumgehung geben könnte, um eine Klasse zu haben zwei verschiedene Arten von einem generischen unterstützen Schnittstelle?

Antwort

11

as mit einem Schnittstellentyp verwendet eine Schnittstelle Cast, die die GUID verwendet, um die Schnittstelle zu finden. Für eine generische Schnittstelle mit einer GUID erhält jede Instanz die gleiche GUID. Wenn ein einzelner Typ mehrere Kopien der Schnittstelle implementiert, führt das Nachschlagen nach GUID dazu, dass die erste Schnittstelle zurückgegeben wird.

Das Programm funktioniert wie erwartet, wenn Sie nicht über eine Schnittstelle Guss verwenden und stattdessen eine Schnittstelle Umwandlung wie folgt verwenden:

procedure Test; 
var 
    t : TTest<integer, string>; 
begin 
    t := TTest<integer, string>.Create(39, 'Blah'); 
    ShowMessage(ITest<string>(t).Val); 
    ShowMessage(IntToStr(ITest<Integer>(t).Val)); 
end; 

Ursprünglich, als Generika wurden für Win32 umgesetzt wurden GUIDs nicht erlaubt auf generische Schnittstellen. Die dynamische Abfrage nach generischen Schnittstellen war jedoch für generische Containerszenarien und allgemein als Mechanismus zum Abfragen eines Service Providers nach typspezifischen Services im Kontext eines Algorithmus (wie Sortieren oder Suchen, die Dinge wie Vergleicher und Gleichheitsprüfungen). Es wurde also ein neuer Plan erstellt: Erstelle eine GUID auf der generischen Schnittstelle, erzeuge jedoch einen Hash der Typargumente für generische Instanziierungen und falte (z. B. xor) den Hash in GUID, um eine eindeutige GUID für jede unterschiedliche und inkompatible Instanziierung zu erzeugen. Dies war jedoch spät am Tag, und eine gute Umsetzung war innerhalb der zeitlichen Beschränkungen nicht möglich. Die Anforderung für dynamische Abfragen blieb jedoch bestehen, sodass die GUIDs beibehalten wurden. Deshalb sind die Dinge so, wie sie heute sind.

Um Ihr spezifisches Szenario zu lösen, ist das beste, das ich empfehlen kann, unterschiedliche Nachkommen mit expliziten GUIDs zu verwenden; oder verwenden Sie einen anderen Mechanismus für die Abfrage der Schnittstelle.

+0

Huh. Gut zu wissen. Vielen Dank. Du bist nur eine Goldmine von Informationen Barry. Danke dass du dir die Zeit nimmst. – TrespassersW

+0

D2010 hat schon diesen Hash, Barry? –

+0

Es tut es leider nicht. Ich sollte es mir wirklich anschauen. –

3

Dies ist ein interessantes Problem. Es scheint, dass der Compiler die Schnittstelle immer der zuletzt angegebenen Version der Schnittstelle zuordnet (wenn Sie die Reihenfolge tauschen, ruft sie die andere Methode auf). Dies hat möglicherweise damit zu tun, dass beide Schnittstellen die gleiche GUID-Signatur haben, so dass der Dispatcher verwirrt wird, welche Methode aufgerufen werden soll, wenn er den Schnittstellenaufruf sieht.

Dies scheint ein Fehler zu sein, sollte also über Quality Central gemeldet werden.