Мешают ли дженерики сопоставлению имен интерфейсов?

По сути, я хочу, чтобы класс мог реализовать две разные версии одного и того же универсального интерфейса.

Рассмотрим этот код

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;

Первый ShowMessage отображает «Бла», как я и ожидал, но второй вылетает. Причина сбоя в том, что вызов вызывает T2Val() вместо Val(), как я и ожидал. Очевидно, сопоставление разрешения конфликтов сопоставляет метод для обоих типов интерфейсов, а не только для ITest: T2.

Итак, вот мой вопрос (ы).

Это ошибка? Под чем я подразумеваю, что Embarcadero намеревался поддерживать это и просто неправильно реализовывать? Или у них никогда не было намерения позволять программистам делать что-то подобное? (Честно говоря, я был немного удивлен, что моя тестовая программа вообще скомпилировалась)

Если это ошибка, кто-нибудь знает, может ли быть обходной путь, позволяющий мне иметь один класс, поддерживающий два разных типа одного универсального интерфейса?


person TrespassersW    schedule 11.11.2009    source источник


Ответы (2)


as с типом интерфейса использует приведение интерфейса, которое использует GUID для поиска интерфейса. Для универсального интерфейса с GUID каждый экземпляр получает один и тот же GUID. Если один тип реализует несколько копий интерфейса, поиск по GUID приведет к возврату первого интерфейса.

Программа работает должным образом, если вы не используете приведение интерфейса, а вместо этого используете преобразование интерфейса, подобное этому:

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;

Первоначально, когда дженерики реализовывались для Win32, GUID не разрешались в универсальных интерфейсах. Однако динамические запросы к универсальным интерфейсам были желательны для универсальных контейнерных сценариев и в целом в качестве механизма запроса поставщика услуг для специфических для типа служб в контексте алгоритма (например, сортировки или поиска, для которых требуются такие вещи, как компараторы и тесты на равенство). Таким образом, был сформирован новый план: иметь GUID на универсальном интерфейсе, но создать хэш аргументов типа для общих экземпляров и свернуть (например, xor) хэш в GUID, чтобы создать уникальный GUID для каждого отдельного и несовместимого экземпляра. Однако это было поздно, и хорошая реализация была невозможна в условиях ограниченного времени. Но требование динамических запросов осталось, поэтому остались и GUID. Вот почему все так обстоит сегодня.

Лучшее, что я могу порекомендовать для решения вашего конкретного сценария, — это использовать отдельных потомков с явными идентификаторами GUID; или используйте другой механизм для запроса интерфейса.

person Barry Kelly    schedule 12.11.2009
comment
Хм. Хорошо знать. Спасибо. Ты просто кладезь информации, Барри. Спасибо, что нашли время. - person TrespassersW; 12.11.2009
comment
К сожалению, это не так. Я действительно должен изучить это, хотя. - person Barry Kelly; 12.11.2009
comment
К сожалению в XE до сих пор нет решения. Но это было бы очень кстати! - person Christian Metzler; 10.01.2011

Это интересная проблема. Похоже, что происходит то, что компилятор всегда сопоставляет интерфейс с последней указанной версией интерфейса (если вы меняете порядок, он вызывает другой метод). Это может быть связано с тем фактом, что оба интерфейса имеют одинаковую подпись GUID, поэтому диспетчер путается в том, какой метод следует вызывать, когда он видит вызов интерфейса.

Похоже, это ошибка, о которой следует сообщить через центр качества.

person skamradt    schedule 12.11.2009