Is it possible to adjust the Parameters of a procedure with an IF statement?

Viewed 130

I have two procedures that almost identical the only difference is the data type of one of the parameters.

procedure InsertNewStringAnswer(AidQuestion: Integer; AAnswer: String);

and

procedure InsertNewBoolAnswer(AidQuestion: Integer; AAnswer: Boolean);

I need to change the answer type based on the question. Do I have to write two procedures and call them with a third or is there a way I can change the data type of the parameter AAnswer at runtime?

I am editing this to show How I build the solution. In this case I used Variant type. I cannot verify if this is best practice but it works:).

procedure InsertNewAnswer(AidQuestion: Integer; AAnswer: Variant);
var
  idNextRecord: string;
  isBoolean: Boolean;
  StrAAnswer: String;
  BoolAAnswer: Boolean;
begin
  With Connection.queryMain Do
  begin
    SQL.Clear;
    SQL.Text := 'select count(*) as summe from dbo.Answer';
    Open;
  end;
  idNextRecord := Connection.queryMain.FieldByName('summe').Asstring;
  With Connection.queryMain Do
  begin
    SQL.Clear;
//Here I check if the question has a boolean or string answer.
    SQL.Text :=('select isBool from dbo.Questions AS ISBOOL WHERE idQuestion= :SQLAidQuestion;');
    ParamByName('SQLAidQuestion').AsInteger := AidQuestion;
    Prepare;
    Open;
  end;
//and write it to a Variable.
  isBoolean := Connection.queryMain.FieldByName('isBool').AsBoolean;
  Connection.queryMain.SQL.Clear;
//I then use a if statement to change the Variant Type accordingly
  if isBoolean = True then
  begin
//Note that System.Variants does not have VarToBool so I use a workaround
    BoolAAnswer := StrToBool(VarToStr(AAnswer));
    AAnswer := BoolAAnswer;
    Connection.queryMain.SQL.Text :=
      ('INSERT INTO Frueherkennung.dbo.Answer' +
      '(idAnswer, idQuestion, Answer)VALUES(' + idNextRecord +
      ', :sqlQuestion, :sqlAAnswer);');
    Connection.queryMain.ParamByName('sqlQuestion').AsInteger := AidQuestion;
    Connection.queryMain.ParamByName('sqlAAnswer').AsBoolean := AAnswer;
  end
  else
  begin
    StrAAnswer := VarToStr(AAnswer);
    MessageDlg('iSBool:= False', mtError, [mbok], 0);
    AAnswer := StrAAnswer;
    Connection.queryMain.SQL.Text :=('INSERT INTO Frueherkennung.dbo.Answer' +
      '(idAnswer, idQuestion, Answer)VALUES(' + idNextRecord +
      ', :sqlQuestion, :sqlAAnswer);');
    Connection.queryMain.ParamByName('sqlQuestion').AsInteger := AidQuestion;
    Connection.queryMain.ParamByName('sqlAAnswer').Asstring := AAnswer
  end;
  With Connection.queryMain Do
  begin
    Prepare;
    Execute;
    SQL.Clear;
  end;
end;

Thank you everyone for your awesome answers.

2 Answers

You can write a single function if the AAnswer argument is of type variant which allows almost anything in it.

You can also keep two procedures but with same name using the overload keyword.

And you can also have a single procedure taking AAnswer as untyped pointer to the storage the caller want to use. Of course at that moment, the question must contain the necessary information to decide if the point point to a boolean or to a string.

This last option is really not recommended. For me the cleanest solution is using overloaded procedures.

Forgot another possibility: use AAnswer of type array of const.

Sounds like a job for Generics.

In XE7 and later, you can do this:

type
  TQuestion = class
  public
    class procedure InsertNewAnswer<T>(AidQuestion: Integer; AAnswer: T);
  end;

class procedure TQuestion.InsertNewAnswer<T>(AidQuestion: Integer; AAnswer: T);
begin
  case GetTypeKind(T) of
    tkString, tkLString, tkUString, tkWString:
      InsertNewStringAnswer(AidQuestion, AAnswer);
    tkEnumeration:
      if GetTypeData(TypeInfo(T))^.BaseType^ = TypeInfo(Boolean) then
        InsertNewBoolAnswer(AidQuestion, PBoolean(@AAnswer)^);
    ...
  end;
end;

Prior to XE7, you can do this instead:

type
  TQuestion = class
  public
    class procedure InsertNewAnswer<T>(AidQuestion: Integer; AAnswer: T);
  end;

...

uses
  ..., TypInfo;

class procedure TQuestion.InsertNewAnswer<T>(AidQuestion: Integer; AAnswer: T);
begin
  case PTypeInfo(TypeInfo(T)).Kind of
    tkString:
      InsertNewStringAnswer(AidQuestion, PShortString(@AAnswer)^);
    tkLString:
      InsertNewStringAnswer(AidQuestion, PAnsiString(@AAnswer)^);
    tkUString:
      InsertNewStringAnswer(AidQuestion, PUnicodeString(@AAnswer)^);
    tkWString:
      InsertNewStringAnswer(AidQuestion, PWideString(@AAnswer)^);
    tkEnumeration:
      if GetTypeData(TypeInfo(T))^.BaseType^ = TypeInfo(Boolean) then
        InsertNewBoolAnswer(AidQuestion, PBoolean(@AAnswer)^);
    ...
  end;
end;

Either way, you can then call it like this:

TQuestion.InsertNewAnswer<String>(id, '...');
TQuestion.InsertNewAnswer<Boolean>(id, true);
...
Related