Generic linked lists redux

Do generics and typed pointers mix? Certainly, so long as you use a nested type:

type
  TOuterType<T> = class
  public type
    PInnerType = ^TInnerType;
    TInnerType = record
      Value: T;
    end;
  end;

OK, so the New/Dispose bug still holds, but that’s only a minor irritant. I’m not sure why I didn’t think of the nested type ‘trick’ before, and indeed, I’ve now noticed Alexandru Ciobanu uses this sort of thing in his Collections project (which, by the way, is well worth looking at generally). Better late than never though!

Put to work, here’s a simple generic queue implementation based on a singly-linked list. In use, it works just like the stock TQueue<T>:

unit CCR.LinkedQueue;

interface

type
  TLinkedQueue<T> = class
  strict private type
    PNode = ^TNode;
    TNode = record
      NextNode: PNode;
      Value: T;
    end;
  public type
    TEnumerator = record
    strict private
      FDoneFirst: Boolean;
      FNode: PNode;
      function GetCurrent: T;
    public
      constructor Create(AHeadNode: PNode);
      function MoveNext: Boolean;
      property Current: T read GetCurrent;
    end;
  strict private
    FCount: Integer;
    FHeadNode, FTailNode: PNode;
  public
    destructor Destroy; override;
    function GetEnumerator: TEnumerator;
    procedure Clear;
    procedure Enqueue(const Value: T);
    function Dequeue: T;
    function Peek: T;
    property Count: Integer read FCount;
  end;

implementation

uses RTLConsts, Classes;

constructor TLinkedQueue<T>.TEnumerator.Create(AHeadNode: PNode);
begin
  FDoneFirst := False;
  FNode := AHeadNode;
end;

function TLinkedQueue<T>.TEnumerator.GetCurrent: T;
begin
  Result := FNode.Value;
end;

function TLinkedQueue<T>.TEnumerator.MoveNext: Boolean;
begin
  if FDoneFirst then
    FNode := FNode.NextNode
  else
    FDoneFirst := True;
  Result := (FNode <> nil);
end;

destructor TLinkedQueue<T>.Destroy;
begin
  Clear;
  inherited;
end;

function TLinkedQueue<T>.GetEnumerator: TEnumerator;
begin
  Result := TEnumerator.Create(FHeadNode);
end;

procedure TLinkedQueue<T>.Clear;
var
  Node: PNode;
begin
  while FHeadNode <> nil do
  begin
    Node := FHeadNode.NextNode;
    Finalize(FHeadNode.Value);
    FreeMem(FHeadNode);
    FHeadNode := Node;
  end;
  FCount := 0;
end;

procedure TLinkedQueue<T>.Enqueue(const Value: T);
var
  NewNode: PNode;
begin
  NewNode := AllocMem(SizeOf(TNode));
  NewNode.Value := Value;
  if FHeadNode = nil then
    FHeadNode := NewNode
  else
    FTailNode.NextNode := NewNode;
  FTailNode := NewNode;
  Inc(FCount);
end;

function TLinkedQueue<T>.Dequeue: T;
var
  Node: PNode;
begin
  if Count = 0 then
    raise EListError.CreateRes(@SUnbalancedOperation);
  Node := FHeadNode;
  Result := Node.Value;
  FHeadNode := Node.NextNode;
  Finalize(Node);
  FreeMem(Node);
  Dec(FCount);
end;

function TLinkedQueue<T>.Peek: T;
begin
  if Count = 0 then
    raise EListError.CreateRes(@SUnbalancedOperation);
  Result := FHeadNode.Value;
end;

end.

Pretty straightforward I’d say.

What of a generic linked list in a fuller sense though, where you can insert and remove items in any order? While this could be done using pointers, you would then be caught between exposing the pointer stuff directly (in which case, why bother with a generic container in the first place – just implement the pattern directly) and hiding it under an array list-type shell. I’d say it’s better to avoid both by using a class for the node type instead:

unit CCR.SimpleLinkedList;

interface

type
  TSimpleLinkedList<T> = class
  public type
    TNode = class
    strict private
      FPrevious, FNext: TNode;
      FList: TSimpleLinkedList<T>;
      FValue: T;
    protected
      constructor Create(AList: TSimpleLinkedList<T>; APrevious, ANext: TNode;
        const AValue: T); overload;
    public
      constructor Create; overload; //disallow outside construction by raising an exception
      destructor Destroy; override;
      procedure MoveBefore(ANode: TNode);
      procedure MoveAfter(ANode: TNode);
      property List: TSimpleLinkedList<T> read FList;
      property Next: TNode read FNext write FNext;
      property Previous: TNode read FPrevious write FPrevious;
      property Value: T read FValue write FValue;
    end;

    TValueEnumerator = record
    strict private
      FDoneFirst: Boolean;
      FNode: TNode;
      function GetCurrent: T;
    public
      constructor Create(AHead: TNode);
      function MoveNext: Boolean;
      property Current: T read GetCurrent;
    end;
  private
    FCount: Integer;
    FFirst, FLast: TNode;
    procedure ValidateNode(ANode: TNode);
  public
    destructor Destroy; override;
    function GetEnumerator: TValueEnumerator;
    procedure Clear;
    function Add(const AValue: T): TNode; inline;
    function AddAfter(ANode: TNode; const AValue: T): TNode;
    function Insert(Index: Integer; const AValue: T): TNode;
    function InsertBefore(ANode: TNode; const AValue: T): TNode;
    property Count: Integer read FCount;
    property First: TNode read FFirst;
    property Last: TNode read FLast;
  end;

implementation

uses SysUtils, RTLConsts; //for the standard exception types and error messages

{ TNode }

constructor TSimpleLinkedList<T>.TNode.Create;
begin
  raise ENoConstructException.CreateResFmt(@SNoConstruct, [ClassName]);
end;

constructor TSimpleLinkedList<T>.TNode.Create(AList: TSimpleLinkedList<T>;
  APrevious, ANext: TNode; const AValue: T);
begin
  Assert(AList <> nil);
  inherited Create;
  FPrevious := APrevious;
  FNext := ANext;
  if APrevious <> nil then APrevious.Next := Self;
  if ANext <> nil then ANext.Previous := Self;
  if APrevious = AList.Last then AList.FLast := Self;
  if ANext = AList.First then AList.FFirst := Self;
  FList := AList;
  FValue := AValue;
  Inc(AList.FCount);
end;

destructor TSimpleLinkedList<T>.TNode.Destroy;
begin
  Dec(List.FCount);
  if Self = List.First then List.FFirst := Next;
  if Self = List.Last then List.FLast := Previous;
  if Previous <> nil then Previous.FNext := Next;
  if Next <> nil then Next.FPrevious := Previous;
  inherited Destroy;
end;

procedure TSimpleLinkedList<T>.TNode.MoveBefore(ANode: TNode);
begin
  List.ValidateNode(ANode);
  if ANode = Next then Exit;
  //unlink ourselves from our current neighbours
  if Previous <> nil then Previous.Next := Next;
  if Next <> nil then Next.Previous := Previous;
  //link ourselves to our new neighbours
  Previous := ANode.Previous;
  if Previous <> nil then Previous.Next := Self;
  ANode.Previous := Self;
  Next := ANode;
end;

procedure TSimpleLinkedList<T>.TNode.MoveAfter(ANode: TNode);
begin
  List.ValidateNode(ANode);
  if ANode = Previous then Exit;
  //unlink ourselves from our current neighbours
  if Previous <> nil then Previous.Next := Next;
  if Next <> nil then Next.Previous := Previous;
  //link ourselves to our new neighbours
  Next := ANode.Next;
  if Next <> nil then Next.Previous := Self;
  ANode.Next := Self;
  Previous := ANode;
end;

{ TValueEnumerator }

constructor TSimpleLinkedList<T>.TValueEnumerator.Create(AHead: TNode);
begin
  FDoneFirst := False;
  FNode := AHead;
end;

function TSimpleLinkedList<T>.TValueEnumerator.MoveNext: Boolean;
begin
  if not FDoneFirst then
    FDoneFirst := True
  else
    FNode := FNode.Next;
  Result := (FNode <> nil);
end;

function TSimpleLinkedList<T>.TValueEnumerator.GetCurrent: T;
begin
  Result := FNode.Value;
end;

{ TSimpleLinkedList }

destructor TSimpleLinkedList<T>.Destroy;
begin
  Clear;
  inherited Destroy;
end;

function TSimpleLinkedList<T>.GetEnumerator: TValueEnumerator;
begin
  Result := TValueEnumerator.Create(First);
end;

function TSimpleLinkedList<T>.Add(const AValue: T): TNode;
begin
  Result := TNode.Create(Self, Last, nil, AValue);
end;

function TSimpleLinkedList<T>.AddAfter(ANode: TNode; const AValue: T): TNode;
begin
  ValidateNode(ANode);
  Result := TNode.Create(Self, ANode, ANode.Next, AValue);
end;

procedure TSimpleLinkedList<T>.Clear;
begin
  while Count > 0 do First.Free;
end;

function TSimpleLinkedList<T>.Insert(Index: Integer; const AValue: T): TNode;
var
  I: Integer;
  Node: TNode;
begin
  if (Index < 0) or (Index > Count) then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);
  if Index = Count then
    Result := Add(AValue)
  else
  begin
    Node := First;
    for I := 1 to Index do
      Node := Node.Next;
    Result := TNode.Create(Self, Node.Previous, Node, AValue);
  end;
end;

function TSimpleLinkedList<T>.InsertBefore(ANode: TNode; const AValue: T): TNode;
begin
  ValidateNode(ANode);
  Result := TNode.Create(Self, ANode.Previous, ANode, AValue);
end;

procedure TSimpleLinkedList<T>.ValidateNode(ANode: TNode);
begin
  if ANode = nil then
    raise EArgumentNilException.CreateRes(@SArgumentNil);
  if ANode.List <> Self then
    raise EArgumentOutOfRangeException.CreateRes(@SGenericItemNotFound);
end;

end.

Beyond the custom enumerator to support for/in loops, this should be considered a pretty minimal implementation. As with the LinkedList class in the .NET BCL, it deliberately avoids an indexer and instead exposes the node objects directly. To delete an item, you therefore free the node. Here’s it in action:


var
  List: TSimpleLinkedList<string>;
  Fourth, Sixth, Seventh: TSimpleLinkedList<string>.TNode;
  S: string;
begin
  ReportMemoryLeaksOnShutdown := True;
  List := TSimpleLinkedList<string>.Create;
  try
    Fourth := List.Add('Fourth');
    List.InsertBefore(Fourth, 'Second');
    Sixth := List.AddAfter(Fourth, 'Sixth');
    List.InsertBefore(Sixth, 'Fifth');
    Seventh := List.InsertBefore(Fourth, 'Seventh');
    List.Add('Zero');
    List.Insert(0, 'First');
    List.Insert(2, 'Third');
    Seventh.MoveAfter(Sixth);
    List.Last.Free; //this removes the node from the list
    for S in List do
      WriteLn(S);
  finally
    List.Free;
  end;
  ReadLn;
end.

I’ve put up a slightly ‘fuller-fat’ version here if anyone’s interested. Compared to the one presented above, it fits into the framework of Generics.Collections (meaning, it descends from TEnumerable<T> and uses IComparer<T>), adds most of the methods from the stock TList<T>, and implements optional object ownership when instantiated with a class.

Weird in more ways than one

To illustrate an annoying limitation of Delphi generics regarding typed pointers, Lars Fosdal has recently posted some code that provides a half-working generic double linked list implementation (link). The fact it only half works is at least half the point (so to speak), but I’m not sure the limitations of Delphi generics are the only thing making the code ‘weird’ as Lars calls it.

To illustrate, here’s how I would write it – I’ve tried to keep the code as similar to Lars’ version as possible, though the semantics are inevitably slightly different:

program Project1;

{$APPTYPE CONSOLE}

type
  TLinkVisitor<T> = reference to procedure(const Item: T);

  TDoubleLinked<T> = record
    Prev: ^TDoubleLinked<T>;
    Next: ^TDoubleLinked<T>;
    Value: T;
    class function Create(const aValue: T): Pointer; static;
    function Add(const aValue: T): Pointer;
    procedure Delete;
    procedure DeleteAll;
    function First: Pointer;
    function Last: Pointer;
    procedure ForEach(const Proc: TLinkVisitor<T>);
  end;

{ TDoubleLinked<T> }

class function TDoubleLinked<T>.Create(const aValue: T): Pointer;
var
  Node: ^TDoubleLinked<T>;
begin
  Node := AllocMem(SizeOf(TDoubleLinked<T>));
  Node.Value := aValue;
  Result := Node;
end;

function TDoubleLinked<T>.Add(const aValue: T): Pointer;
var
  NewNode: ^TDoubleLinked<T>;
begin
  NewNode := AllocMem(SizeOf(TDoubleLinked<T>));
  NewNode.Value := aValue;
  NewNode.Prev := @Self;
  if Next <> nil then
  begin
    NewNode.Next := Next;
    Next.Prev := Pointer(NewNode);
  end;
  Result := NewNode;
  Next := Result;
end;

procedure TDoubleLinked<T>.Delete;
begin
  if Prev <> nil then Prev.Next := Next;
  if Next <> nil then Next.Prev := Prev;
  Finalize(Self);
  FreeMem(@Self);
end;

procedure TDoubleLinked<T>.DeleteAll;
var
  Node, Next: ^TDoubleLinked<T>;
begin
  Node := First;
  repeat
    Next := Pointer(Node.Next);
    Finalize(Node^);
    FreeMem(Node);
    Node := Next;
  until Node = nil;
end;

function TDoubleLinked<T>.First: Pointer;
var
  Node: ^TDoubleLinked<T>;
begin
  Node := @Self;
  while Node.Prev <> nil do Node := Pointer(Node.Prev);
  Result := Node;
end;

function TDoubleLinked<T>.Last: Pointer;
var
  Node: ^TDoubleLinked<T>;
begin
  Node := @Self;
  while Node.Next <> nil do Node := Pointer(Node.Next);
  Result := Node;
end;

procedure TDoubleLinked<T>.ForEach(const Proc: TLinkVisitor<T>);
var
  Node: ^TDoubleLinked<T>;
begin
  Node := First;
  repeat
    Proc(Node.Value);
    Node := Pointer(Node.Next);
  until Node = nil;
end;

var
  List, Node, SavedNode: ^TDoubleLinked<String>;
begin
  List := TDoubleLinked<String>.Create('One');
  Node := List.Add('Two');
  Node := Node.Add('Three');
  SavedNode := Node;
  Node := Node.Add('Four');
  SavedNode.Add('ThreeAndAHalf');

  List.ForEach(
    procedure(const Value:String)
    begin
      WriteLn('List: ' + Value)
    end);

  Node.ForEach(
    procedure(const Value:String)
    begin
      WriteLn('Node: ' + Value)
    end);

  List.DeleteAll;
  Write('Press ENTER to exit...');
  ReadLn;
end.

OK, so there are some workarounds here that shouldn’t be necessary. In particular, we have to return a raw Pointer for Create, Add, First and Last, and since New and Dispose don’t work correctly with generic record types (that’s an outright bug BTW), AllocMem and Finalize/FreeMem pairs must be used instead. Despite that, the code isn’t radically different to how you would write a non-generic equivalent.

Implementing generic helpers as metaclasses

While it wasn’t the main point of the post, I did mention previously the possibility of using metaclasses as generic helpers, and how this can be preferable to using interfaces. An esteemed commentator then suggested the example I gave was far too trivial to be generalised, and in particular, that the hacky cleverness of Generics.Defaults was all very necessary. This rather sounded like a challenge, and one I should take up — so I have!

Continue reading

Generic arithmetic

In terms of syntax, the capabilities of Delphi generics are closely modelled on C#’s implementation, at least as found in C# 2.0, even down to certain quirks that are otherwise difficult to explain — e.g., Delphi using ‘record’ as the name for the general value type constraint (C# uses ‘struct’ for the same thing, with the same overloading of meaning). Modelling as much as possible on the C# implementation is in some ways a good thing (I’ve been reading up on Java generics recently, and ‘oh dear’ is all I have to say about them!). In other ways it’s a bit annoying though, the negative aspects in my view being centred on the limited set of available constraints.

One particularly significant omission here are operator constraints, i.e. the ability to specify that a type parameter must/can/will suppport the + operator or whatever. Looking over the C# side of the fence, I’ve read of various ways of circumventing this. An early suggestion — advocated by a certain Anders Hejlsberg when generics were first introduced to C# — was to define a set of helper classes (specifically, an abstract generic base class and one concrete descendant per type). The basic idea here is similar to the one used by the Delphi RTL’s generic collection classes (IComparer and all that). Personally I find Generics.Defaults’ use of interfaces all a bit heavyweight-yet-hacky (truly, ‘IInterface=IUnknown’ is to Delphi what ‘generic type instantiation=type erasure’ is to Java…). So, for experimental purposes, I came up with the following metaclass-using variant instead (much cleaner IMO!). As this is just a test, I’ve only defined an Add helper method, though in principle others like Substract and Multiply could be given too:

uses
  SysUtils,
  Generics.Collections,
  Diagnostics;

type
  TCalculator<T> = class abstract
    class function Add(const A, B: T): T; virtual; abstract;
  end;

  TDoubleCalculator = class(TCalculator<Double>)
    class function Add(const A, B: Double): Double; override;
  end;

  TIntegerCalculator = class(TCalculator<Integer>)
    class function Add(const A, B: Integer): Integer; override;
  end;

  TSingleCalculator = class(TCalculator<Single>)
    class function Add(const A, B: Single): Single; override;
  end;

  TGenericMaths<T; Calculator: TCalculator<T>> = record
    class function Sum(List: TList<T>): T; static;
  end;

class function TDoubleCalculator.Add(const A, B: Double): Double;
begin
  Result := A + B;
end;

class function TIntegerCalculator.Add(const A, B: Integer): Integer;
begin
  Result := A + B;
end;

class function TSingleCalculator.Add(const A, B: Single): Single;
begin
  Result := A + B;
end;

class function TGenericMaths<T, Calculator>.Sum(List: TList<T>): T;
var
  Elem: T;
begin
  Result := Default(T);
  for Elem in List do
    Result := Calculator.Add(Result, Elem);
end;

All pretty trivial I hope you agree. The next question, though, is how the virtual method calls would impact performance, so I next wrote the following test code —

var
  I, IntElem, IntTotal: Integer;
  DoubleElem, DoubleTotal: Double;
  DoubleList: TList<Double>;
  IntList: TList<Integer>;
  SingleElem, SingleTotal: Single;
  SingleList: TList<Single>;
  Stopwatch: TStopwatch;
begin
  Randomize;
  Writeln('Testing integers - generating data...');
  IntList := TList<Integer>.Create;
  try
    IntList.Capacity := $1FFFFFF;
    for I := 1 to IntList.Capacity do
      IntList.Add(Random(20));
    for I := 1 to 3 do
    begin
      Stopwatch := TStopwatch.StartNew;
      IntTotal := TGenericMaths<Integer, TIntegerCalculator>.Sum(IntList);
      Stopwatch.Stop;
      Writeln('TGenericMaths calculated ', IntTotal, ' and took ',
        Stopwatch.ElapsedMilliseconds, 'ms');
      Stopwatch := TStopwatch.StartNew;
      IntTotal := 0;
      for IntElem in IntList do
        IntTotal := IntTotal + IntElem;
      Stopwatch.Stop;
      Writeln('    Doing things manually calculated ', IntTotal, ' and took ',
        Stopwatch.ElapsedMilliseconds, 'ms');
    end;
  finally
    IntList.Free;
  end;
  Writeln('');
  Writeln('Testing doubles - generating data...');
  DoubleList := TList<Double>.Create;
  try
    DoubleList.Capacity := $FFFFFF;
    for I := 1 to DoubleList.Capacity do
      DoubleList.Add(Random);
    for I := 1 to 3 do
    begin
      Stopwatch := TStopwatch.StartNew;
      DoubleTotal := TGenericMaths<Double, TDoubleCalculator>.Sum(DoubleList);
      Stopwatch.Stop;
      Writeln('TGenericMaths calculated ', DoubleTotal, ' and took ',
        Stopwatch.ElapsedMilliseconds, 'ms');
      Stopwatch := TStopwatch.StartNew;
      DoubleTotal := 0;
      for DoubleElem in DoubleList do
        DoubleTotal := DoubleTotal + DoubleElem;
      Stopwatch.Stop;
      Writeln('    Doing things manually calculated ', DoubleTotal, ' and took ',
        Stopwatch.ElapsedMilliseconds, 'ms');
    end;
  finally
    DoubleList.Free;
  end;
  Writeln('');
  Writeln('Testing singles - generating data...');
  SingleList := TList<Single>.Create;
  try
    SingleList.Capacity := $1FFFFFF;
    for I := 1 to SingleList.Capacity do
      SingleList.Add(Random);
    for I := 1 to 3 do
    begin
      Stopwatch := TStopwatch.StartNew;
      SingleTotal := TGenericMaths<Single, TSingleCalculator>.Sum(SingleList);
      Stopwatch.Stop;
      Writeln('TGenericMaths calculated ', SingleTotal, ' and took ',
        Stopwatch.ElapsedMilliseconds, 'ms');
      Stopwatch := TStopwatch.StartNew;
      SingleTotal := 0;
      for SingleElem in SingleList do
        SingleTotal := SingleTotal + SingleElem;
      Stopwatch.Stop;
      Writeln('    Doing things manually calculated ', SingleTotal, ' and took ',
        Stopwatch.ElapsedMilliseconds, 'ms');
    end;
  finally
    SingleList.Free;
  end;
  ReadLn;
end.

Leaving aside the irony of massive code duplication in code to do with generics (ahem), here’s a screenshot of some example output when I run the resulting program on my laptop (click on the graphic to see it at full size):

This was using the default release build configuration. Using the default debug configuration, the numbers are much worse all round obviously, but the difference between the generic and manual single data test is somewhat narrower, for reasons I don’t have the knowledge to suggest.

Anyhow, plainly, nothing would be as good as operator constraints being implemented by the compiler. Nonetheless, there are apparently alternative methods available in C# now that drastically narrow or even remove the performance gap between generic and manual approaches, if perhaps at the cost of compile-time type checking (see here or here). So far as I can tell, these rely on things not available in Delphi-land. I wonder whether any performance geek has ideas to the contrary…?

var
I, IntElem, IntTotal: Integer;
DoubleElem, DoubleTotal: Double;
DoubleList: TList<Double>;
IntList: TList<Integer>;
SingleElem, SingleTotal: Single;
SingleList: TList<Single>;
Stopwatch: TStopwatch;
begin
Randomize;
Writeln(‘Testing integers – generating data…’);
IntList := TList<Integer>.Create;
try
IntList.Capacity := $1FFFFFF;
for I := 1 to IntList.Capacity do
IntList.Add(Random(20));
for I := 1 to 3 do
begin
Stopwatch := TStopwatch.StartNew;
IntTotal := TGenericMaths<Integer, TIntegerCalculator>.Sum(IntList);
Stopwatch.Stop;
Writeln(‘TGenericMaths calculated ‘, IntTotal, ‘ and took ‘,
Stopwatch.ElapsedMilliseconds, ‘ms’);
Stopwatch := TStopwatch.StartNew;
IntTotal := 0;
for IntElem in IntList do
IntTotal := IntTotal + IntElem; //well, Inc(IntTotal, IntElem) would be better…
Stopwatch.Stop;
Writeln(‘    Doing things manually calculated ‘, IntTotal, ‘ and took ‘,
Stopwatch.ElapsedMilliseconds, ‘ms’);
end;
finally
IntList.Free;
end;
Writeln(”);
Writeln(‘Testing doubles – generating data…’);
DoubleList := TList<Double>.Create;
try
DoubleList.Capacity := $FFFFFF;
for I := 1 to DoubleList.Capacity do
DoubleList.Add(Random);
for I := 1 to 3 do
begin
Stopwatch := TStopwatch.StartNew;
DoubleTotal := TGenericMaths<Double, TDoubleCalculator>.Sum(DoubleList);
Stopwatch.Stop;
Writeln(‘TGenericMaths calculated ‘, DoubleTotal, ‘ and took ‘,
Stopwatch.ElapsedMilliseconds, ‘ms’);
Stopwatch := TStopwatch.StartNew;
DoubleTotal := 0;
for DoubleElem in DoubleList do
DoubleTotal := DoubleTotal + DoubleElem;
Stopwatch.Stop;
Writeln(‘    Doing things manually calculated ‘, DoubleTotal, ‘ and took ‘,
Stopwatch.ElapsedMilliseconds, ‘ms’);
end;
finally
DoubleList.Free;
end;
Writeln(”);
Writeln(‘Testing singles – generating data…’);
SingleList := TList<Single>.Create;
try
SingleList.Capacity := $1FFFFFF;
for I := 1 to SingleList.Capacity do
SingleList.Add(Random);
for I := 1 to 3 do
begin
Stopwatch := TStopwatch.StartNew;
SingleTotal := TGenericMaths<Single, TSingleCalculator>.Sum(SingleList);
Stopwatch.Stop;
Writeln(‘TGenericMaths calculated ‘, SingleTotal, ‘ and took ‘,
Stopwatch.ElapsedMilliseconds, ‘ms’);
Stopwatch := TStopwatch.StartNew;
SingleTotal := 0;
for SingleElem in SingleList do
SingleTotal := SingleTotal + SingleElem;
Stopwatch.Stop;
Writeln(‘    Doing things manually calculated ‘, SingleTotal, ‘ and took ‘,
Stopwatch.ElapsedMilliseconds, ‘ms’);
end;
finally
SingleList.Free;
end;
ReadLn;