0

So I'm working in Delphi 2007 and I am cleaning up my code. I have come to notice that in a great many procedures I declare a number of different variables of the same type.

for example the one procedure I am looking at now I declare 4 different string lists and I have to type var1 := TStringList.Create for each one.

I had the idea to make a procedure that took in an open array of variables, my list of 4 variables and then create them all. The call would be something like this

CreateStringLists([var1,var2,var3,var4]);

But as to my knowledge you cannot pass the open array by reference and therefore not do what I was hoping to. Does anyone have any interesting ideas about this?

Tim
  • 1,549
  • 1
  • 20
  • 37

5 Answers5

5

Often in refactoring you need to take a very wide view of the code. Why "cleanup" a couple of operations like this, when most likely you shouldn't be doing any of these operations at all?

In this case, it seems suspicous to me that you have one routine that needs to deal with 4 separate string lists. That doesn't seem very likely to have good cohesion. Perhaps instead it should be one string list-handling routine called four times. So I'd really like to see the entire routine, rather than comment on how to make this one nit in it prettier.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
  • I am developing some simplistic presentation software, and this relates specifically to a slide (power point esq) presentation. And upon each slide I have 3 different areas where text can go: The Body, Title, Copyright. And then another string list that deals with parsing text from the text file that is how the slides are saved to disk. They all are created for a unique purpose. I do like your answer ... I'm gonna think about this. – Tim Jan 05 '11 at 20:26
4

You can do anything (or nearly anything) with Delphi. I don't recommend the following code to use, just to know that the trick is possible:

type
  PStringList = ^TStringList;

procedure CreateStringLists(const SL: array of PStringList);
var
  I: Integer;

begin
  for I:= 0 to High(SL) do begin
    SL[I]^:= TStringList.Create;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  SL1, SL2, SL3: TStringList;

begin
  CreateStringLists([@SL1, @SL2, @SL3]);
  SL3.Add('123');
  Caption:= SL3[0];
  SL1.Free;
  SL2.Free;
  SL3.Free;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
kludg
  • 27,213
  • 5
  • 67
  • 118
  • +1 I doubt I could bring myself to do this, but it's typesafe and quite sneaky – nice! – David Heffernan Jan 05 '11 at 20:30
  • +1 You beat me! I now have my own implementation based on plain pointers, I was just upgrading it to use pointers to TStringList when noticed your answer. :) – jachguate Jan 05 '11 at 20:39
  • I think, of course, you have to make it exception aware on the CreateStringLists procedure, so all of them are succesfully created, or all the created ones are freed before exiting the routine on an exceptional way. – jachguate Jan 05 '11 at 20:41
  • @jachguate - the code is just illustration. It can be improved if you are about to use it in real project. For example pointers can be nilled prior to calling constructors, procedure replaced by function that returns the number of objects created, or else. – kludg Jan 05 '11 at 21:17
  • Why make it an array of pointers? TStringLists already are references. – Nick Hodges Jan 06 '11 at 02:34
  • But @Nick, the purpose of this function is to set those references' *values*. Passing a reference variable by value doesn't allow you to change that variable's value. – Rob Kennedy Jan 06 '11 at 03:53
  • 1
    @Serg: In pubic places like SO I'm in favor to write robust illustration code or, at least, to point out the way to make it robust in a descriptive manner. Take a look at @David Heffernan answer on this question. – jachguate Jan 06 '11 at 16:15
  • @jachguate Thanks for the nice comment but I'm finding it hard to get past your typo! – David Heffernan Jan 06 '11 at 19:12
  • @David: I don't get that typo! maybe that's why I wrote what I wrote thinking it is correct, can you please enlighten me about where that mistake is? – jachguate Jan 06 '11 at 21:22
1

You could create a series of overloaded versions with 2, 3, 4 etc. parameters. For example:

procedure CreateStringLists(var L1, L2: TStringList); overload;
procedure CreateStringLists(var L1, L2, L3: TStringList); overload;
procedure CreateStringLists(var L1, L2, L3, L4: TStringList); overload;

procedure CreateStringLists(var L1, L2: TStringList);
begin
  L1 := nil;
  L2 := nil;
  Try
    L1 := TStringList.Create;
    L2 := TStringList.Create;
  Except
    FreeAndNil(L2);
    FreeAndNil(L1);
    raise;
  End;
end;

// etc.

If I were doing this, I'd write a script to generate the code.

As an aside, in my own code, I would write InitialiseNil(L1, L2) at the start of that function, and FreeAndNil(L2, L1) in the exception handler. InitialiseNil and FreeAndNil are functions generated by a very simple Python script that is included in the codebase as a comment so that it can be re-run. A routine like CreareStringLists as defined above is only useful if you have a matching routine to free them all in one shot. This allows you to write:

CreateStringLists(L1, L2);
Try
  // do stuff with L1, L2
Finally
  FreeAndNil(L2, L1);
End;

Finally, I'm not saying that I would necessarily do this, but this is meant as a naive and direct answer to the question. As @T.E.D. states, the need to do this suggests deeper problems in the codebase.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
1

Actually, what's the problem with 4 constructors?

Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48
1

If it makes sense in your context, you can aggregate declarations inside a specialized TObjectList.

type
  TMyList<T:class,constructor> = class(TObjectList<T>)
  public
    procedure CreateItems(const ACount : integer);
  end;

procedure TMyList<T>.CreateItems(const ACount: integer);
var
  Index: Integer;
begin
  for Index := 0 to (ACount - 1) do Add(T.Create);
end;

// Test procedure
procedure TestMe;
var
  MyStringsList : TMyList<TStringList>;
begin
  MyStringsList := TMyList<TStringList>.Create(True);
  MyStringsList.CreateItems(10);
  // ...
  FreeAndNil(MyStringsList);
end;

So you can specialized your list.

TridenT
  • 4,879
  • 1
  • 32
  • 56