Передать различные типы записей в качестве параметра в процедуре?

Есть ли хитрость для передачи записей с другим типом в качестве параметра в процедуре? Например, посмотрите на этот псевдокод:

type
  TPerson = record
    Species: string;
    CountLegs: Integer;
  end;

  TSpider = record
    Species: string;
    CountLegs: Integer;
    Color: TColor;
  end;

var
  APerson: TPerson;
  ASpider: TSpider;

// Is there a trick to pass different record types as parameter in a procedure?:
procedure DoSomethingWithARecord(const ARecord: TAbstractRecord?);
begin
  if ARecord is TPerson then
    DoSomethingWithThisPerson(ARecord as TPerson)
  else if ARecord is TSpider then
    DoSomethingWithThisSpider(ARecord as TSpider);
end;  

procedure DefineRecords;
begin
  APerson.Species := 'Human';
  APerson.CountLegs := 2;
  ASpider.Species := 'Insect';
  ASpider.CountLegs := 8;
  ASpider.Color := clBtnFace;
  DoSomethingWithARecord(APerson);
  DoSomethingWithARecord(ASpider);
end;

1 ответ

Решение

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

type
  TRecordType = (rtPerson, rtSpider);

procedure DoSomething(RecordType: TRecordType; const ARecord);
begin
  case RecordType of
  rtPerson:
    DoSomethingWithThisPerson(TPerson(ARecord));
  rtSpider:
    DoSomethingWithThisSpider(TSpider(ARecord));
  end;
end;

Вы можете подумать о том, чтобы поместить код типа в первое поле каждой записи:

type
  TPerson = record
    RecordType: TRecordType;
    Species: string;
    CountLegs: Integer;
  end;

  TSpider = record
    RecordType: TRecordType;
    Species: string;
    CountLegs: Integer;
    Color: TColor;
  end;

function GetRecordType(ARecord): TRecordType;
begin
  Result := TRecordType(ARecord);
end;

....

procedure DoSomething(const ARecord);
begin
  case GetRecordType(ARecord) of
  rtPerson:
    DoSomethingWithThisPerson(TPerson(ARecord));
  rtSpider:
    DoSomethingWithThisSpider(TSpider(ARecord));
  end;
end;

Вы можете использовать дженерики:

type
  TMyRecordDispatcher = record
    class procedure DoSomething<T: record>(const Value: T); static;
  end;

class procedure TMyRecordDispatcher.DoSomething<T>(const Value: T); 
begin
  if TypeInfo(T) = TypeInfo(TPerson) then
    DoSomethingWithThisPerson(PPerson(@Value)^)
  else if TypeInfo(T) = TypeInfo(TSpider) then
    DoSomethingWithThisSpider(PSpider(@Value)^);
end;

И вызвать функции следующим образом:

TMyRecordDispatcher.DoSomething(APerson);
TMyRecordDispatcher.DoSomething(ASpider);

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

На мой взгляд, все это грязно и хрупко. Многое из вышеперечисленных переопределений во время выполнения метода отправки, полиморфизм. Классы больше подходят для этого. Я не поддерживаю любой из кода выше.

С другой стороны, возможно, это все излишне. Что случилось с:

DoSomethingWithThisPerson(Person);
DoSomethingWithThisSpider(Spider);

Поскольку вы знаете типы во время компиляции, зачем выбирать что-то более сложное?

Вы можете использовать перегрузку функций, чтобы можно было опустить тип в имени функции.

procedure DoSomething(const APerson: TPerson); overload;
begin
  ....
end;

procedure DoSomething(const ASpider: TSpider); overload;
begin
  ....
end;

....

DoSomething(Person);
DoSomething(Spider);
Другие вопросы по тегам