How can I expose this TList from an interface, as either IEnumerator
or IEnumerator
? I am using Delphi XE.
Here\'s h
If you really want to make a class that implements IEnumerable
unit uGenericEnumerable;
interface
uses SysUtils, Classes, Generics.Collections;
type TGenericEnumerator = class(TInterfacedObject, IEnumerator, IEnumerator)
private
FList: TList;
FIndex: Integer;
protected
function GenericGetCurrent: T;
public
constructor Create(AList: TList);
procedure Reset;
function MoveNext: Boolean;
function GetCurrent: TObject;
function IEnumerator.GetCurrent = GenericGetCurrent;
property Current: T read GenericGetCurrent;
end;
type TNonGenericEnumerable = class(TInterfacedObject, IEnumerable)
protected
function GetNonGenericEnumerator: IEnumerator; virtual; abstract;
public
function IEnumerable.GetEnumerator = GetNonGenericEnumerator;
end;
type TGenericEnumerable = class(TNonGenericEnumerable, IEnumerable)
private
FList: TList;
public
constructor Create;
destructor Destroy; override;
function GetNonGenericEnumerator: IEnumerator; override;
function GetEnumerator: IEnumerator;
property List: TList read FList;
end;
implementation
{ TGenericEnumerator }
constructor TGenericEnumerator.Create(AList: TList);
begin
inherited Create;
FList := AList;
FIndex := -1;
end;
procedure TGenericEnumerator.Reset;
begin
FIndex := -1;
end;
function TGenericEnumerator.MoveNext: Boolean;
begin
if FIndex < FList.Count then
begin
Inc(FIndex);
Result := FIndex < FList.Count;
end
else
begin
Result := False;
end;
end;
function TGenericEnumerator.GenericGetCurrent: T;
begin
Result := FList[FIndex];
end;
function TGenericEnumerator.GetCurrent: TObject;
begin
// If T has not been constrained to being a class, raise an exception instead of trying to return an object.
raise Exception.Create('Cannot use this as a non-generic enumerator');
// If T has been constrained to being a class, return GenericGetCurrent.
// Result := GenericGetCurrent;
end;
{ TGenericEnumerable }
constructor TGenericEnumerable.Create;
begin
inherited Create;
FList := TList.Create;
end;
destructor TGenericEnumerable.Destroy;
begin
FList.Free;
end;
function TGenericEnumerable.GetEnumerator: IEnumerator;
begin
Result := TGenericEnumerator.Create(FList);
end;
function TGenericEnumerable.GetNonGenericEnumerator: IEnumerator;
begin
Result := GetEnumerator;
end;
end.
Now, your FungibleTrollUnit will look something like this:
unit FungibleTrollUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Generics.Collections,
uGenericEnumerable;
type
IFungibleTroll = interface
['{03536137-E3F7-4F9B-B1F5-2C8010A4D019}']
function GetTrollName:String;
function GetTrollRetailPrice:Double;
end;
IFungibleTrolls = interface(IEnumerable)
['{090B45FB-2925-4BFC-AE97-5D3F54E1C575}']
function GetTrolls: TList;
function FindSingleItemByName(aName:String):IFungibleTroll;
function FindMultipleItemsByName(aName:String):IEnumerable;
property Trolls:TList read GetTrolls;
end;
TFungibleTrolls = class (TGenericEnumerable, IFungibleTrolls, IEnumerable)
public
function GetTrolls: TList;
function FindSingleItemByName(aName: String): IFungibleTroll;
function FindMultipleItemsByName(aName: String): IEnumerable;
property Trolls:TList read GetTrolls;
private
end;
implementation
uses StrUtils;
{ TFungibleTrolls }
function TFungibleTrolls.GetTrolls: TList;
begin
Result := List;
end;
function TFungibleTrolls.FindMultipleItemsByName(aName: String): IEnumerable;
var FilteredTrolls: TGenericEnumerable;
var Troll: IFungibleTroll;
begin
FilteredTrolls := TGenericEnumerable.Create;
for Troll in List do
begin
if Troll.GetTrollName = aName then
FilteredTrolls.List.Add(Troll);
end;
Result := IEnumerable(FilteredTrolls);
end;
function TFungibleTrolls.FindSingleItemByName(aName: String): IFungibleTroll;
var Troll: IFungibleTroll;
begin
Result := nil;
for Troll in List do
begin
if Troll.GetTrollName = aName then
Result := Troll;
break;
end;
end;
end.
Note that the implementation of IEnumerable does not work, but IEnumerable
This is because, unless T is constrained, you cannot convert a T to a TObject.
If T is a string or an integer, for example, then the IEnumerator does not have a TObject to return.
If T is constrained to be a class, you can get the implementation of IEnumerable to work.
If you constrain T to be an IInterface, you could get IEnumerable to work (Delphi 2010 and after (cast GenericGetCurrent to an IInterface, then to a TObject)), but I doubt if that is an advantage.
I would rather use it without the constraints, and do without being able to iterate everything as TObjects.
TGenericEnumerable
Even though you can implement TGenericEnumerable
type TGenericEnumerable = class(TInterfacedObject, IEnumerable, IEnumerable)
private
FList: TList;
protected
function GenericGetEnumerator: IEnumerator;
public
constructor Create;
destructor Destroy; override;
function GetEnumerator: IEnumerator;
function IEnumerable.GetEnumerator = GenericGetEnumerator;
property List: TList read FList;
end;
the disadvantage of doing this is that if you try to iterate using the TGenericEnumerable
Usual caveats about mixing references to an interface and its underlying object. If you refer to an object both as an object type and as an IEnumerable<....>, then when the interface reference count goes back to zero, the object will be freed even if you still have a reference to it as an object. (That's why I defined IFungibleTrolls; so that I could refer to the collection as an interface).
You can make alternative implementations of TGenericEnumerator