Search code examples
delphirecorddelphi-xe7

Pass different record types as parameter in a procedure?


Is there a trick to pass records with different type as parameter in a procedure? For example, look at this pseudo-code:

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;

Solution

  • Record instances don't contain type information in the same way that classes do. So you would need to pass an extra argument to indicate which type you were working with. For instance:

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

    You might contemplate putting the type code in the first field of each record:

    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;
    

    You could use generics:

    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;
    

    And call the functions like this:

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

    This uses generic type inference and so allows you not to explicitly state the type. Although as an example of generics it makes me cringe. Please don't do this.

    In my view all of this is messy and brittle. Much of the above reimplements run time method dispatch, polymorphism. Classes are more suited to this. I don't endorse any of the code above.

    On the other hand, perhaps this is all needless. What's wrong with:

    DoSomethingWithThisPerson(Person);
    DoSomethingWithThisSpider(Spider);
    

    Since you know the types at compile time, why opt for anything more complex?

    You could use function overloading to make it possible to omit the type from the function name.

    procedure DoSomething(const APerson: TPerson); overload;
    begin
      ....
    end;
    
    procedure DoSomething(const ASpider: TSpider); overload;
    begin
      ....
    end;
    
    ....
    
    DoSomething(Person);
    DoSomething(Spider);