18.06.2011

Delphi and TStringCase

Delphi's Case <X> of statement works with everything that is Ordinal. integer, char, enumerations, it even works with boolean.
case b of
  true : MakeSomething;
  false : DoNotMakeSomething;
end;

However, it does not work with strings. And I've come across some places where it would be very handy to have a Case <String> of.

Luckily, with Delphi 2010 came anonymous methods and with the combination of a Dictionary we can finally have something that works as a Case <String> of.
type
  TProc = reference to procedure;
  TCase<T> = class
  private
    fDict : TDictionary<T,TProc>;
    fElse: TProc;
  public
    constructor Create;
    destructor Destroy; override;
    procedure addEntry(aKey : T; aProc :TProc);
    procedure addEntries(const aKeys : array of T; aProc : TProc);
    procedure switch(aKey : T);
    property ElseCase : TProc read fElse write fElse;
  end;
  TStringCase = TCase<string>;

TStringCase is the type, that will be used the most, but with the generic TCase, (almost) everything can be used for a Case statement.

The implementation is very simple:
procedure TCase<T>.addEntries(const aKeys: array of T; aProc: TProc);
var
  key : T;
begin
  for key in aKeys do
    addEntry(key,aProc);
end;

procedure TCase<T>.addEntry(aKey: T; aProc: TProc);
begin
  fDict.Add(aKey, aProc);
end;

constructor TCase<T>.Create;
begin
  fDict := TDictionary<T,TProc>.Create;
end;

destructor TCase<T>.Destroy;
begin
  fDict.Free;
  inherited;
end;

procedure TCase<T>.switch(aKey: T);
var
  p : TProc;
begin
  if fDict.TryGetValue(aKey, p) then
    p()
  else
    if assigned(fElse) then fElse();
end;

And this is how it's used:
procedure UseStringCase;
var
  sc : TStringCase;
begin
  sc :=TStringCase.Create;
  try
    // setup the case
    sc.addEntry('STRING1',procedure
    begin
      Label1.Caption := 'First Option';
    end);

    sc.addEntry('STRING1',procedure
    begin
      Label1.Caption := 'Second Option';
    end);

    sc.addEntries(['STRING3','STRING4','STRING5'],procedure
    begin
      Label1.Caption := 'one of three options';
    end);

    sc.ElseProc := procedure
    begin
      Label1.Caption := 'Something else';
    end;

    sc.switch(Edit1.Text); // execute the right procedure
  finally
    sc.Free;
  end;

end;

The usage has some overhead compared to the standard case, but I think it is still prettier than a bunch of if-then-else's.
procedure UseIfThenElse;
var
  s : string;
begin
  s := Edit1.Text;
  if s = 'STRING1' then
  begin
    Label1.Caption := 'First Option';
  end
  else if s = 'STRING2' then
  begin
    Label1.Caption := 'Second Option';
  end
  else if (s = 'STRING3') or (s = 'STRING4') or (s = 'STRING5') then
  begin
    Label1.Caption := 'one of three options';
  end
  else
  begin
    Label1.Caption := 'Something else';
  end;
end;

Download is available here.

Keine Kommentare: