Преобразование из интерфейса в класс завершается неудачно при вводе дополнительного имени для интерфейса - почему? - PullRequest
2 голосов
/ 10 мая 2011

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

Рассмотрим следующие определения типов:

  IDummy<T> = interface
  end;
  TMyRecord = record
    Intf:IDummy<Byte>;
  end;
  TDummy = class(TInterfacedObject, IDummy<Byte>)
  public
  end;

  IThisBreaksIt = IDummy<Byte>; // <== this line triggers the error

А теперь простой код, который использует типы:

var
  ARecord:TMyRecord;
  Item:IDummy<Byte>;

  ImplWorks,
  ImplBroken:TDummy;
begin
  ARecord.Intf:=TDummy.Create;
  Item:=ARecord.Intf;

  ImplWorks:=TDummy(Item);
  ImplBroken:=TDummy(ARecord.Intf); // <== resulting instance is broken
end;

Так что я делаю, сохраняя ссылку на интерфейсвнутри записи.Теперь я хочу привести это обратно к реализующему классу с жестким приведением.

Вот загвоздка: это не удастся, если я определю псевдоним для моего интерфейса (IThisBreaksIt = IDummy<Byte>).Закомментируйте эту строку и ImplBroken больше не прерывается.В сломанном случае адреса ImplWorks и ImplBroken различны;вместо этого адреса Item и ImplBroken теперь совпадают.Кажется, что автоматик, ответственный за хард-кастинг, не в состоянии.

Дополнительный вывод: замена TDummy(ARecord.Intf) на ARecord.Intf as TDummy исправляет это.

Это дало мне некоторую головную боль, потому что это было похороненов куче кода, и я не ожидал такого поведения.Это нормально?

Редактировать для Cosmin :

Пример для тяжелого приведения интерфейса к объекту.

Протестировано в XE: работает (указатели StreamAdaptIntf и StreamAdaptImpl отличаются; утверждение успешно выполнено) Протестировано в 2009 году: происходит сбой (указатели StreamAdaptIntf и StreamAdaptImpl совпадают; утверждение не выполняется)

uses ActiveX;

var
  Stream:TStream;
  StreamAdaptIntf:IStream;
  StreamAdaptImpl:TStreamAdapter;
begin
  Stream:=TMemoryStream.Create;
  StreamAdaptIntf:=TStreamAdapter.Create(Stream, soOwned);

  StreamAdaptImpl:=TStreamAdapter(StreamAdaptIntf);
  Assert(Integer(StreamAdaptImpl) <> Integer(StreamAdaptIntf));
end;

1 Ответ

0 голосов
/ 31 июля 2011

Вероятно, это не поможет, этот вопрос задавался давным-давно, но для тех, кто проверяет это в будущем ...

Возможно, вы только что опубликовали тестовый код, но ваш интерфейс должен содержать GUID, GUIDоднозначно определяет интерфейс к компилятору (Ctrl + Shift + G в Delphi 2009).

IDummy<T> = interface
  ['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
  procedure DoSomething;
end;

Жесткое приведение обычно небезопасно.Жесткое приведение в действие допустимо только в том случае, если вы знаете, что тип объекта будет правильным.При приведении предпочтительнее проверять его тип перед приведением следующим образом ...

var
  lTypeA: TTypeA;
begin
  if ObjectA is TTypeA then begin
    lTypeA := TTypeA(ObjectA);
  end;

Еще лучше, если я выполню приведение "как", что, я думаю, вызовет исключение, если оно недопустимо.Это действительно предпочтительнее!Я написал код, который выполняет hardcast только для того, чтобы тратить часы и часы, выясняя, что на самом деле мое приведение было неверным ... Delphi не скажет вам, если вы приведете к неверному типу, тогда, когда вы позже используете объект, вы в конечном итогев целом отладочный кошмар.As вызовет исключение и покажет вам проблему в вашем коде

var
  lTypeA: TTypeA;
begin
  if ObjectA is TTypeA then begin
    // Will raise an exception if ObjectA is not TTypeA, 
    // in this simple case the ObjectA is TTypeA test is redundant
    lTypeA := ObjectA as TTypeA;
  end;

Я бы на самом деле приводил только объекты между объектами в delphi.В Delphi есть полезная функция «поддержка», которая определяет, реализует ли объект интерфейс и возвращает ли вам экземпляр этого интерфейса.Затем вы можете использовать возвращаемую локальную переменную для выполнения любой необходимой вам функции из интерфейса

if Supports(ImplBroken, IThisBreaksIt, lObjInterface) then
begin
  lObjInterface.DoSomething;
end;

Полный код ...

type
  // Generic IDummy interface
  IDummy<T> = interface
    ['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
    procedure DoSomething;
  end;

// Don't alias interfaces if possible
// This is a more specific version of your interface
IThisBreaksIt = interface(IDummy<Byte>)
  ['{76EFA371-4674-4190-8A4B-06850103C1D8}']
end;

TMyRecord = record
  // I would suggest, if you can avoid it don't store interfaces 
  // in a record, just simple types - just my opinion, delphi doesn't stop you
  Intf: IDummy<Byte>;
end;

// Remember this is interfaced, so the object only exists while refcount > 0
TDummy = class(TInterfacedObject, IDummy<Byte>)
protected
  { IDummy<T> }
  // interface methods should be protected
  // So ideally we refer to the interface of this object, 
  // not publicly available methods
  procedure DoSomething;
public
end;


var
  ARecord: TMyRecord;
  Item: IDummy<Byte>; // Think this should be IThisBreaksIt

  ImplWorks:TDummy;
  ImplBroken: IThisBreaksIt;
  lObjInterface: IThisBreaksIt;
begin
  ARecord.Intf := TDummy.Create;
  Item := ARecord.Intf;

  // Nasty hard cast here - what if your interfaced object is destroyed
  // before you reach this code section?
  ImplWorks := TDummy(Item);

  // This line compiles, buts it's really not the right way to get the interface
  ImplBroken := IThisBreaksIt(ARecord.Intf);

  // I believe this is the right way to get the interface for an object
  if Supports(ARecord.Intf, IThisBreaksIt, lObjInterface) then
  begin
    lObjInterface.DoSomething;
  end;
end;
...