wades
wades

Reputation: 947

Is it possible to write this generic in Delphi?

I have some data types:

  type
    TICD10CodeMap = TObjectDictionary<string, TICD10LookupResult>;
    TStringMap = TDictionary<string, string>;
    TFMRecMap = TDictionary<string, TFilemanRecord>;

And some instances of them:

  var
    FICD10Codes: TICD10CodeMap;
    FPatientTypes: TStringMap;
    FPOVs: TFMRecMap;
    FTreatmentTypes: TStringMap;
    FTypesOfCare: TStringMap;

And I had a method that was happily populating them, using their Add methods, until I discovered that my data source could have duplicate keys in it.

Now I could just write code with ContainsKey before each and every Add() and do something, but I thought I would be clever:

procedure AddPair<ValType, DictType: TDictionary<string, ValType>>
    (Key: string; Val: ValType;
    Dict: DictType);
begin
  if (Dict as TDictionary<string, ValType>).ContainsKey(Key) then
    AddPair('Copy of ' + Key, Val, Dict)
  else
    Dict.Add(Key, Val);
end;

But it seems I am too clever for Delphi. First off, there's that cast in the body of the function definition, which seems like it ought to be unnecessary, then there's the fact that when I try to call AddPair, I get compiler errors. The naive AddPair(s3, s2, FPatientTypes) gets me both

[dcc32 Error] uReverseVistaLookups.pas(116): E2010 Incompatible types: 'ValType' and 'string'
[dcc32 Error] uReverseVistaLookups.pas(116): E2010 Incompatible types: 'DictType' and 'System.Generics.Collections.TDictionary<System.string,System.string>'

while the would-be-more-sophisticated AddPair<string, TStringMap>(s3, s2, FPatientTypes) complains about

[dcc32 Error] uReverseVistaLookups.pas(127): E2515 Type parameter 'ValType' is not compatible with type 'System.Generics.Collections.TDictionary<System.string,System.string>'

Is there some incantation that I'm missing, which would make Delphi out of what I'm trying to do here?

Upvotes: 0

Views: 1075

Answers (2)

J...
J...

Reputation: 31403

While this seems like an odd way to use a TDictionary, an easy way to get what you want is simply to subclass.

program Project1;
{$APPTYPE CONSOLE}
uses
  Generics.Collections, SysUtils;

type
  TCopyKeyMap<TValue> = class(TDictionary<string, TValue>)
    public
      procedure AddWithCopy(const Key: string; const Value: TValue);
  end;
  TStringMap = TCopyKeyMap<string>;

procedure TCopyKeyMap<TValue>.AddWithCopy(const Key: string; const Value: TValue);
begin
  if ContainsKey(Key) then
    AddWithCopy('Copy of ' + Key, Value)
  else
    Add(Key, Value);
end;

var
  sm : TStringMap;
  sp : TPair<string, string>;
begin
  sm := TStringMap.Create;
  try
    sm.AddWithCopy('foo', 'bar');
    sm.AddWithCopy('foo', 'bat');
    sm.AddWithCopy('foo', 'bam');
    for sp in sm do WriteLn(Format('%s : %s', [sp.Key,sp.Value]));
  finally
    sm.Free;
  end;
  ReadLn;
end.

Upvotes: 1

wades
wades

Reputation: 947

D'oh.

There is no need for two type parameters in the generic:

procedure AddPair<ValType>(Key: string; Val: ValType;
    Dict: TDictionary<string, ValType>);

is easy to write (without the troublesome cast!) and does what it should.

Upvotes: 3

Related Questions