I am using generics to define a list of objects that themselves hold generic lists. I have written a method for retrieving the aggregate of each of these lists using specific methods that basically do the same thing. Below is the structure:
unit Unit1;
interface
uses
System.Generics.Collections;
type
TListType=(ltSType,ltFType);
TMyList<T> = class(TList<T>)
{Do some stuff in here to help load lists etc}
end;
TListObject=class(TObject)
private
FSList:TMyList<string>;
FIList:TMyList<integer>;
function GetSList: TMyList<string>;
function GetIList: TMyList<integer>;
public
property MySList:TMyList<string> read GetSList;
property MyIList:TMyList<integer> read GetIList;
constructor create;
end;
TListOfObject<T:TListObject> = class(TObjectList<T>)
public
Function AggrSList:TMyList<string>;
Function AggrIList:TMyList<integer>;
end;
implementation
{ TListObject }
constructor TListObject.create;
begin
FSList:=TMyList<string>.create;
FIList:=TMyList<integer>.create;
end;
function TListObject.GetIList: TMyList<integer>;
begin
result:=FIlist;
end;
function TListObject.GetSList: TMyList<string>;
begin
result:=FSList;
end;
{ TListOfObject<T> }
function TListOfObject<T>.AggrIList: TMyList<integer>;
var
i,j:integer;
begin
result:=TMyList<integer>.create;
for I := 0 to count-1 do
for j := 0 to items[i].MyIList.Count-1 do
result.Add(items[i].MyIList[j]);
end;
function TListOfObject<T>.AggrSList: TMyList<string>;
var
i,j:integer;
begin
result:=TMyList<string>.create;
for I := 0 to count-1 do
for j := 0 to items[i].MySList.Count-1 do
result.Add(items[i].MySList[j]);
end;
end.
I am still fairly new to generics but feel that the aggregate methods AggrIList and AggrSlist could be written using generics that uses a single method to extract the data then is cast at the result.
Is this possible and how would I approach this? I plan to do some more advanced functions that would benefit from this approach as well.
You can use (pseudocode):
TNestedList<T> = class(TList<TList<T>>)
function GetAggregateList : TList<T>;
end;
function TNestedList<T>.GetAggregateList : TList<T>;
var
List : TList<T>;
Item : T;
begin
Result := TList<T>.Create;
for List in Self do
for Item in List do
Result.Add(Item);
end;
No need to implement GetAggregateList for specific types - that's what you use generics for.
Also note that it might be better to either call your method CreateAggregateList (to make clear that the caller is responsible for destroying the created list) or to pass in an already created list instance into a procedure:
procedure GetAggregateList(List : TList<T>);
...which leaves list ownership completely to the caller and is quite a common pattern in Delphi.
Related
I'm refactoring a component code, and I found the follow code:
procedure TMenuToolbarButton.ClearActivation;
var
i: Integer;
begin
for i := 0 to Self.Parent.ComponentCount -1 do
begin
if (Self.Parent.Components[i] is TMenuToolbarButton) then
begin
(Self.Parent.Components[i] as TMenuToolbarButton).FActivatedImage.Visible := False;
(Self.Parent.Components[i] as TMenuToolbarButton).FActivatedImageGrowLeft.Visible := False;
end;
end;
end;
I'ts working perfectly today, but i want to use for/in in this method, something like this:
procedure TMenuToolbarButton.ClearActivation;
var
MyMenuToolbarButton: TMenuToolbarButton;
begin
for MyMenuToolbarButton in Self.Parent do
begin
MyMenuToolbarButton.FActivatedImage.Visible := False;
MyMenuToolbarButton.FActivatedImageGrowLeft.Visible := False;
end;
end;
I already tried with Generics.Collections casting the Self.Parent like this: TObjectList<TMenuToolbarButton>(Self.Parent)
So, I want to know if is there a better way to make the working code more "elegant"
The Parent is a TWinControl, not a TObjectList, so your attempted typecast is invalid.
You can't use a for.. in loop with the Components property directly, as it is not an iterable container that meets any of the documented requirements:
Delphi supports for-element-in-collection style iteration over containers. The following container iteration patterns are recognized by the compiler:
for Element in ArrayExpr do Stmt;
for Element in StringExpr do Stmt;
for Element in SetExpr do Stmt;
for Element in CollectionExpr do Stmt;
for Element in Record do Stmt;
The Components property is not an Array, a String, a Set, a Collection, or a Record, so it can't be iterated by a for..in loop.
However, TComponent itself satisfies the documented requirements of an iterable Collection:
To use the for-in loop construct on a class or interface, the class or interface must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes:
The class or interface must contain a public instance method called GetEnumerator(). The GetEnumerator() method must return a class, interface, or record type.
The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext() method must return a Boolean. The for-in loop calls this method first to ensure that the container is not empty.
The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The type of the Current property must be the type contained in the collection.
TComponent has a public GetEnumerator() method which returns a TComponentEnumerator object that internally iterates the Components property. But, since the property deals with TComponent objects, you will still have to manually typecast them inside the loop.
Try this:
procedure TMenuToolbarButton.ClearActivation;
var
//i: Integer;
Comp: TComponent;
Btn: TMenuToolbarButton;
begin
//for i := 0 to Self.Parent.ComponentCount -1 do
for Comp in Self.Parent do
begin
//Comp := Self.Parent.Components[i];
if Comp is TMenuToolbarButton then
begin
Btn := TMenuToolbarButton(Comp);
Btn.FActivatedImage.Visible := False;
Btn.FActivatedImageGrowLeft.Visible := False;
end;
end;
end;
So, using a for..in loop does not really gain you anything useful over a traditional for..to loop in this situation.
TComponent implements method GetEnumerator by returning instance of TComponentEnumerator, which enumerates all components owned by this component. In order to use this enumerator you could change your local variable declaration to var MyMenuToolbarButton: TComponent;, but you would still need to type-cast inside the loop.
If you really, really want to use for..in loop for enumerating components of specified type, you can write your own generic enumerator:
type
TComponentEnumerator<T: TComponent> = record
private
FIndex: Integer;
FComponent: TComponent;
public
constructor Create(AComponent: TComponent);
function GetCurrent: T; inline;
function GetEnumerator: TComponentEnumerator<T>;
function MoveNext: Boolean;
property Current: T read GetCurrent;
end;
constructor TComponentEnumerator<T>.Create(AComponent: TComponent);
begin
FIndex := -1;
FComponent := AComponent;
end;
function TComponentEnumerator<T>.GetCurrent: T;
begin
Result := T(FComponent.Components[FIndex]);
end;
function TComponentEnumerator<T>.GetEnumerator: TComponentEnumerator<T>;
begin
Result := Self;
end;
function TComponentEnumerator<T>.MoveNext: Boolean;
begin
Inc(FIndex);
while (FIndex < FComponent.ComponentCount) and (not (FComponent.Components[FIndex] is T)) do
Inc(FIndex);
Result := FIndex < FComponent.ComponentCount;
end;
Usage:
procedure TMenuToolbarButton.ClearActivation;
var
MyMenuToolbarButton: TMenuToolbarButton;
begin
for MyMenuToolbarButton in TComponentEnumerator<TMenuToolbarButton>.Create(Self.Parent) do
begin
MyMenuToolbarButton.FActivatedImage.Visible := False;
MyMenuToolbarButton.FActivatedImageGrowLeft.Visible := False;
end;
end;
Few notes:
This is pretty naïve implementation which is not protected against some edge cases like changing the components collection while iterating, cross-thread access, ... Of course your original code does none of that, but when you write general purpose class, you should consider making it more foolproof or document its limitations.
This implementation enumerates components of type T or its descendants.
Using enumerators adds small overhead when compared to simple for..to loop.
I'm using a very large delphi third party library without source code, this library has several classes with abstract methods. I need to determine when an abtract method is implemented by a Descendant class in runtime to avoid the EAbstractError: Abstract Error and shows a custom message to the user or use another class instead.
for example in this code I want to check in runtime if the MyAbstractMethod is implemented.
type
TMyBaseClass = class
public
procedure MyAbstractMethod; virtual; abstract;
end;
TDescendantBase = class(TMyBaseClass)
public
end;
TChild = class(TDescendantBase)
public
procedure MyAbstractMethod; override;
end;
TChild2 = class(TDescendantBase)
end;
How I can determine if an abstract method is implemented in a Descendant class in runtime?
you can use the Rtti, the GetDeclaredMethods function get a list of all the methods that are declared in the reflected (current) type. So you can check if the method is present in the list returned by this function.
function MethodIsImplemented(const AClass:TClass;MethodName : string): Boolean;
var
m : TRttiMethod;
begin
Result := False;
for m in TRttiContext.Create.GetType(AClass.ClassInfo).GetDeclaredMethods do
begin
Result := CompareText(m.Name, MethodName)=0;
if Result then
break;
end;
end;
or you can compare the Parent.Name property of the TRttiMethod and check if match with the current class name.
function MethodIsImplemented(const AClass:TClass;MethodName : string): Boolean;
var
m : TRttiMethod;
begin
Result := False;
m:=TRttiContext.Create.GetType(AClass.ClassInfo).GetMethod(MethodName);
if m<>nil then
Result:=CompareText(AClass.ClassName,m.Parent.Name)=0;
end;
function ImplementsAbstractMethod(AObj: TMyBaseClass): Boolean;
type
TAbstractMethod = procedure of object;
var
BaseClass: TClass;
BaseImpl, Impl: TAbstractMethod;
begin
BaseClass := TMyBaseClass;
BaseImpl := TMyBaseClass(#BaseClass).MyAbstractMethod;
Impl := AObj.MyAbstractMethod;
Result := TMethod(Impl).Code <> TMethod(BaseImpl).Code;
end;
Look at the implementation of the 32-bit version of the TStream.Seek() method in the VCL source code (in Classes.pas). It performs a check to make sure the 64-bit version of Seek() has been overridden before calling it. It doesn't involve TRttiContext lookups to do that, just a simple loop through its Parent/Child VTable entries, similar to how Zoë's answer shows.
Following up on my earlier question :
Generics and Marshal / UnMarshal. What am I missing here?
In "part #1" (the link above) TOndrej provided a nice solution - that failed on XE2.
Here I provide corrected source to correct that.
And I feel the need to expand this issue a bit more.
So I would like to hear you all how to do this :
First - To get the source running on XE2 and XE2 update 1 make these changes :
Marshal.RegisterConverter(TTestObject,
function (Data: TObject): String // <-- String here
begin
Result := T(Data).Marshal.ToString; // <-- ToString here
end
);
Why ??
The only reason I can see must be related to XE2 is having a lot more RTTI information available. And hence it will try and marshal the TObject returned.
Am I on the right track here? Please feel free to comment.
More important - the example does not implement an UnMarshal method.
If anyone can produce one and post it here I would love it :-)
I hope that you still have interest in this subject.
Kind Regards
Bjarne
In addition to the answer to this question, I've posted a workaround to your previous question here: Generics and Marshal / UnMarshal. What am I missing here?
For some reason, using the non-default constructor of the TJsonobject causes the issue in XE2 - using the default constructor "fixed" the problem.
First, you need to move your TTestobject to its own unit - otherwise, RTTI won't be able to find/create your object when trying to unmarshal.
unit uTestObject;
interface
uses
SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections, DbxJson, DbxJsonReflect;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([vcPublished]) FIELDS([vcPrivate])}
TTestObject=class(TObject)
private
aList:TStringList;
public
constructor Create; overload;
constructor Create(list: array of string); overload;
constructor Create(list:TStringList); overload;
destructor Destroy; override;
function Marshal:TJSonObject;
class function Unmarshal(value: TJSONObject): TTestObject;
published
property List: TStringList read aList write aList;
end;
implementation
{ TTestObject }
constructor TTestObject.Create;
begin
inherited Create;
aList:=TStringList.Create;
end;
constructor TTestObject.Create(list: array of string);
var
I:Integer;
begin
Create;
for I:=low(list) to high(list) do
begin
aList.Add(list[I]);
end;
end;
constructor TTestObject.Create(list:TStringList);
begin
Create;
aList.Assign(list);
end;
destructor TTestObject.Destroy;
begin
aList.Free;
inherited;
end;
function TTestObject.Marshal:TJSonObject;
var
Mar:TJSONMarshal;
begin
Mar:=TJSONMarshal.Create();
try
Mar.RegisterConverter(TStringList,
function(Data:TObject):TListOfStrings
var
I, Count:Integer;
begin
Count:=TStringList(Data).Count;
SetLength(Result, Count);
for I:=0 to Count-1 do
Result[I]:=TStringList(Data)[I];
end);
Result:=Mar.Marshal(Self) as TJSonObject;
finally
Mar.Free;
end;
end;
class function TTestObject.Unmarshal(value: TJSONObject): TTestObject;
var
Mar: TJSONUnMarshal;
L: TStringList;
begin
Mar := TJSONUnMarshal.Create();
try
Mar.RegisterReverter(TStringList,
function(Data: TListOfStrings): TObject
var
I, Count: Integer;
begin
Count := Length(Data);
Result:=TStringList.Create;
for I := 0 to Count - 1 do
TStringList(Result).Add(string(Data[I]));
end
);
//UnMarshal will attempt to create a TTestObject from the TJSONObject data
//using RTTI lookup - for that to function, the type MUST be defined in a unit
Result:=Mar.UnMarshal(Value) as TTestObject;
finally
Mar.Free;
end;
end;
end.
Also note that the constructor has been overloaded - this allows you to see that the code is functional without pre-pouplating the data in the object during creation.
Here is the implementation for the generic class list object
unit uTestObjectList;
interface
uses
SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections,
DbxJson, DbxJsonReflect, uTestObject;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
TTestObjectList<T:TTestObject,constructor> = class(TObjectList<T>)
public
function Marshal: TJSonObject;
constructor Create;
class function Unmarshal(value: TJSONObject): TTestObjectList<T>; static;
end;
//Note: this MUST be present and initialized/finalized so that
//delphi will keep the RTTI information for the generic class available
//also, it MUST be "project global" - not "module global"
var
X:TTestObjectList<TTestObject>;
implementation
{ TTestObjectList<T> }
constructor TTestObjectList<T>.Create;
begin
inherited Create;
//removed the add for test data - it corrupts unmarshaling because the data is already present at creation
end;
function TTestObjectList<T>.Marshal: TJSonObject;
var
Marshal: TJsonMarshal;
begin
Marshal := TJSONMarshal.Create;
try
Marshal.RegisterConverter(TTestObjectList<T>,
function(Data: TObject): TListOfObjects
var
I: integer;
begin
SetLength(Result,TTestObjectlist<T>(Data).Count);
for I:=0 to TTestObjectlist<T>(Data).Count-1 do
Result[I]:=TTestObjectlist<T>(Data)[I];
end
);
Result := Marshal.Marshal(Self) as TJSONObject;
finally
Marshal.Free;
end;
end;
class function TTestObjectList<T>.Unmarshal(value: TJSONObject): TTestObjectList<T>;
var
Mar: TJSONUnMarshal;
L: TStringList;
begin
Mar := TJSONUnMarshal.Create();
try
Mar.RegisterReverter(TTestObjectList<T>,
function(Data: TListOfObjects): TObject
var
I, Count: Integer;
begin
Count := Length(Data);
Result:=TTestObjectList<T>.Create;
for I := 0 to Count - 1 do
TTestObjectList<T>(Result).Unmarshal(TJSONObject(Data[I]));
end
);
//UnMarshal will attempt to create a TTestObjectList<TTestObject> from the TJSONObject data
//using RTTI lookup - for that to function, the type MUST be defined in a unit,
//and, because it is generic, there must be a GLOBAL VARIABLE instantiated
//so that Delphi keeps the RTTI information avaialble
Result:=Mar.UnMarshal(Value) as TTestObjectList<T>;
finally
Mar.Free;
end;
end;
initialization
//force delphi RTTI into maintaining the Generic class information in memory
x:=TTestObjectList<TTestObject>.Create;
finalization
X.Free;
end.
There are several things that are important to note:
If a generic class is created at runtime, RTTI information is NOT kept unless there is a globally accessible object reference to that class in memory. See here: Delphi: RTTI and TObjectList<TObject>
So, the above unit creates such a variable and leaves it instantiated as discussed in the linked article.
The main procedure has been updated that shows both marshaling and unmarshaling the data for both objects:
procedure Main;
var
aTestobj,
bTestObj,
cTestObj : TTestObject;
aList,
bList : TTestObjectList<TTestObject>;
aJsonObject,
bJsonObject,
cJsonObject : TJsonObject;
s: string;
begin
aTestObj := TTestObject.Create(['one','two','three','four']);
aJsonObject := aTestObj.Marshal;
s:=aJsonObject.ToString;
Writeln(s);
bJsonObject:=TJsonObject.Create;
bJsonObject.Parse(BytesOf(s),0,length(s));
bTestObj:=TTestObject.Unmarshal(bJsonObject) as TTestObject;
writeln(bTestObj.List.Text);
writeln('TTestObject marshaling complete.');
readln;
aList := TTestObjectList<TTestObject>.Create;
aList.Add(TTestObject.Create(['one','two']));
aList.Add(TTestObject.Create(['three']));
aJsonObject := aList.Marshal;
s:=aJsonObject.ToString;
Writeln(s);
cJSonObject:=TJsonObject.Create;
cJSonObject.Parse(BytesOf(s),0,length(s));
bList:=TTestObjectList<TTestObject>.Unmarshal(cJSonObject) as TTestObjectList<TTestObject>;
for cTestObj in bList do
begin
writeln(cTestObj.List.Text);
end;
writeln('TTestObjectList<TTestObject> marshaling complete.');
Readln;
end;
Here is my own solution.
As I am very fond of polymorphism, I actually also want a solution that can be built into an object hierarchy. Lets say TTestObject and TTestObjectList is our BASE object. And from that we descend to TMyObject and also TMyObjectList. And furthermore I've made changes to both Object and List - added properties for Marshaller/UnMarshaller
TMyObject = class(TTestObject) and TMyObjectList<T:TMyObject> = class(TTestObjectList)
With this we now introduce some new problems. Ie. how to handle marshalling of different types between lines in the hierarchy and how to handle TJsonMarshal and TJsonUnMarshal as properties on TTestObject and List.
This can be overcome by introducing two new methods on TTestObject level. Two class functions called RegisterConverters and RegisterReverters. Then we go about and change the marshal function of TTestObjectList into a more simpel marshalling.
Two class functions and properties for both object and List.
class procedure RegisterConverters(aClass: TClass; aMar: TJSONMarshal); virtual;
class procedure RegisterReverters(aClass: TClass; aUnMar: TJSONUnMarshal); virtual;
property Mar: TJSONMarshal read FMar write SetMar;
property UnMar: TJSONUnMarshal read FUnMar write SetUnMar;
The Marshal function of List can now be done like this:
function TObjectList<T>.Marshal: TJSONObject;
begin
if FMar = nil then
FMar := TJSONMarshal.Create(); // thx. to SilverKnight
try
RegisterConverters; // Virtual class method !!!!
try
Result := FMar.Marshal(Self) as TJSONObject;
except
on e: Exception do
raise Exception.Create('Marshal Error : ' + e.Message);
end;
finally
ClearMarshal; // FreeAndNil FMar and FUnMar if assigned.
end;
end;
Sure we can still have a marshaller for our TTestObject - but the Marshal function of TTestObjectList will NOT use it. This way only ONE Marshaller will get created when calling Marshal of TTestObjectList (or descendants). And this way we end up getting marshalled ONLY the information we need to recreate our structure when doing it all backwards - UnMarshalling :-)
Now this actually works - but I wonder if anyone has any comments on this ?
Lets add a property "TimeOfCreation" to TMyTestObject:
property TimeOfCreation : TDateTime read FTimeOfCreation write FTimeOfCreation;
And set the property in the constructor.
FTimeofCreation := now;
And then we need a Converter so we override the virtual RegisterConverters of TTestObject.
class procedure TMyTestObject.RegisterConverters(aClass: TClass; aMar: TJSONMarshal);
begin
inherited; // instanciate marshaller and register TTestObject converters
aMar.RegisterConverter(aClass, 'FTimeOfCreation',
function(Data: TObject; Field: String): string
var
ctx: TRttiContext;
date: TDateTime;
begin
date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
end);
end;
I end up with Very simple source like using TTestObject ie.
aList := TMyTestObjectList<TMyTestObject>.Create;
aList.Add(TMyTestObject.Create(['one','two']));
aList.Add(TMyTestObject.Create(['three']));
s := (aList.Marshal).ToString;
Writeln(s);
And now I have succeded in marshalling with polymorphism :-)
This also works with UnMarshalling btw. And Im in the process of rebuilding my FireBird ORM to produce source for all my objects like this.
The current OLD version can be found here :
http://code.google.com/p/objectgenerator/
Remember that it only works for FireBird :-)
I'm new to generics and need some help to structure a class and implement methods.
I'm trying to use generics to serialize any TObject-JSON. Moreover, I want to be able to reuse the code.
These are my questions:
How do I create a generic constructor? I want to be able to use Self or Default(T), but it returns just nil.
V := Marshal.Marshal(ReturnObject) - This method requires a TObject, but I do not know how to reference the current object that was passed in.
How can I use this inside a method? Look at the code snipped below, marked with "Question 3".
This is my code:
TFileOperationResult = class(TObject)
private
FSuccess: Boolean;
//Error: PException;
FLastError: Integer;
function GetFailure: Boolean;
property Failure: Boolean read GetFailure;
public
property Success: Boolean read FSuccess write FSuccess;
property LastError: Integer read FLastError write FLastError;
end;
TResponseObject<T: class> = class(TObject)
private
FReturnObject: T;
function GetReturnObject: T;
function BaseStringsConverter(Data: TObject): TListOfStrings;
public
constructor Create; overload;
property ReturnObject: T read GetReturnObject;
procedure Serialize;
end;
constructor TResponseObject<T>.Create;
begin
// Question 1 - What should go in here?
end;
function TResponseObject<T>.GetReturnObject: T;
begin
Result := Default(T);// Is this correct?
end;
procedure TResponseObject<T>.Serialize;
var
Marshal: TJSONMarshal;
V: TJSONValue;
begin
Marshal := TJSONMarshal.Create(TJSONConverter.Create);
Marshal.RegisterConverter(TResponseObject<T>, BaseStringsConverter);
V := Marshal.Marshal(ReturnObject); // Question 2 - How Can I refer to 'Self'?
OutPut := V.ToString;
Marshal.Free;
end;
Calling code:
procedure TForm1.Test;
var
FileOperationResult: TResponseObject<TFileOperationResult>;
begin
FileOperationResult := TResponseObject<TFileOperationResult>.Create;
FileOperationResult.Serialize;
end;
Question 3:
procedure TForm1.MoveCopyFile<THowNowResponse>(ASource, DDestination: String);
var
FileOperationResult: TFileOperationResult;
begin
FileOperationResult := TFileOperationResult.Create;
// What to do?
end;
Any other comments are much appreciated.
It's hard to tell exactly what you're trying to do here, but I can make a guess. For TResponseObject, you want an object that can contain another object and operate on it. In that case, you probably want to pass it in to the constructor, like so:
constructor TResponseObject<T>.Create(value: T);
begin
FReturnObject := value;
end;
Likewise, if you make a GetReturnObject method, it should probably return the value of the FReturnObject field. (Or you could make the read accessor of the property just reference FReturnObject directly.)
function TResponseObject<T>.GetReturnObject: T;
begin
Result := FReturnObject;
end;
It's really hard to answer #3 since I don't know what you're trying to do with this, but hopefully my answers to the first two will help you get back on track. Just remember that generics don't have to be confusing; they're basically just type substitution. Anywhere you'd use a normal type in a non-generic routine, you can replace it with a <T> to create a generic routine, and then substitute any type that fits the constraints for that particular T.
Using Delphi 2010, let's say I've got a class declared like this:
TMyList = TList<TMyObject>
For this list Delphi kindly provides us with an enumerator, so we can write this:
var L:TMyList;
E:TMyObject;
begin
for E in L do ;
end;
The trouble is, I'd like to write this:
var L:TMyList;
E:TMyObject;
begin
for E in L.GetEnumerator('123') do ;
end;
That is, I want the ability to provide multiple enumerators for the same list, using some criteria. Unfortunately the implementation of for X in Z requires the presence of a function Z.GetEnumerator, with no parameters, that returns the given enumerator! To circumvent this problem I'm defining an interface that implements the "GetEnumerator" function, then I implement a class that implements the interface and finally I write a function on TMyList that returns the interface! And I'm returning an interface because I don't want to be bothered with manually freeing the very simple class... Any way, this requires a LOT of typing. Here's how this would look like:
TMyList = class(TList<TMyObject>)
protected
// Simple enumerator; Gets access to the "root" list
TSimpleEnumerator = class
protected
public
constructor Create(aList:TList<TMyObject>; FilterValue:Integer);
function MoveNext:Boolean; // This is where filtering happens
property Current:TTipElement;
end;
// Interface that will create the TSimpleEnumerator. Want this
// to be an interface so it will free itself.
ISimpleEnumeratorFactory = interface
function GetEnumerator:TSimpleEnumerator;
end;
// Class that implements the ISimpleEnumeratorFactory
TSimpleEnumeratorFactory = class(TInterfacedObject, ISimpleEnumeratorFactory)
function GetEnumerator:TSimpleEnumerator;
end;
public
function FilteredEnum(X:Integer):ISimpleEnumeratorFactory;
end;
Using this I can finally write:
var L:TMyList;
E:TMyObject;
begin
for E in L.FilteredEnum(7) do ;
end;
Do you know a better way of doing this? Maybe Delphi does support a way of calling GetEnumerator with a parameter directly?
Later Edit:
I decided to use Robert Love's idea of implementing the enumerator using anonymous methods and using gabr's "record" factory to save yet an other class. This allows me to create a brand new enumerator, complete with code, using just a few lines of code in a function, no new class declaration required.
Here's how my generic enumerator is declared, in a library unit:
TEnumGenericMoveNext<T> = reference to function: Boolean;
TEnumGenericCurrent<T> = reference to function: T;
TEnumGenericAnonim<T> = class
protected
FEnumGenericMoveNext:TEnumGenericMoveNext<T>;
FEnumGenericCurrent:TEnumGenericCurrent<T>;
function GetCurrent:T;
public
constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>);
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
TGenericAnonEnumFactory<T> = record
public
FEnumGenericMoveNext:TEnumGenericMoveNext<T>;
FEnumGenericCurrent:TEnumGenericCurrent<T>;
constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>);
function GetEnumerator:TEnumGenericAnonim<T>;
end;
And here's a way to use it. On any class I can add a function like this (and I'm intentionally creating an enumerator that doesn't use a List<T> to show the power of this concept):
type Form1 = class(TForm)
protected
function Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
end;
// This is all that's needed to implement an enumerator!
function Form1.Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
var Current:Integer;
begin
Current := From - 1;
Result := TGenericAnonEnumFactory<Integer>.Create(
// This is the MoveNext implementation
function :Boolean
begin
Inc(Current);
Result := Current <= To;
end
,
// This is the GetCurrent implementation
function :Integer
begin
Result := Current;
end
);
end;
And here's how I'd use this new enumerator:
procedure Form1.Button1Click(Sender: TObject);
var N:Integer;
begin
for N in Numbers(3,10) do
Memo1.Lines.Add(IntToStr(N));
end;
See DeHL ( http://code.google.com/p/delphilhlplib/ ). You can write code that looks like this:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
Just like you can do in .NET (no syntax linq of course).
You approach is fine. I don't know of any better way.
Enumerator factory can also be implemented as a record instead of an interface.
Maybe you'll get some ideas here.
Delphi For in loop support requires on of the following: (From the Docs)
Primitive types that the compiler
recognizes, such as arrays, sets or
strings
Types that implement
IEnumerable
Types that implement the
GetEnumerator pattern as documented
in the Delphi Language Guide
If you look at Generics.Collections.pas you will find the implementation for TDictionary<TKey,TValue> where it has three enumerators for TKey, TValue, and TPair<TKey,TValue> types. Embarcadero shows that they have used verbose implementation.
You could do something like this:
unit Generics.AnonEnum;
interface
uses
SysUtils,
Generics.Defaults,
Generics.Collections;
type
TAnonEnumerator<T> = class(TEnumerator<T>)
protected
FGetCurrent : TFunc<TAnonEnumerator<T>,T>;
FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>;
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>;
aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>);
end;
TAnonEnumerable<T> = class(TEnumerable<T>)
protected
FGetCurrent : TFunc<TAnonEnumerator<T>,T>;
FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>;
function DoGetEnumerator: TEnumerator<T>; override;
public
Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>;
aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>);
end;
implementation
{ TEnumerable<T> }
constructor TAnonEnumerable<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>;
aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>);
begin
FGetCurrent := aGetCurrent;
FMoveNext := aMoveNext;
end;
function TAnonEnumerable<T>.DoGetEnumerator: TEnumerator<T>;
begin
result := TAnonEnumerator<T>.Create(FGetCurrent,FMoveNext);
end;
{ TAnonEnumerator<T> }
constructor TAnonEnumerator<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>;
aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>);
begin
FGetCurrent := aGetCurrent;
FMoveNext := aMoveNext;
end;
function TAnonEnumerator<T>.DoGetCurrent: T;
begin
result := FGetCurrent(self);
end;
function TAnonEnumerator<T>.DoMoveNext: Boolean;
begin
result := FMoveNext(Self);
end;
end.
This would allow you declare your Current and MoveNext methods anonymously.
You can do away with the factory and the interface if you add a GetEnumerator() function to your enumerator, like this:
TFilteredEnum = class
public
constructor Create(AList:TList<TMyObject>; AFilterValue:Integer);
function GetEnumerator: TFilteredEnum;
function MoveNext:Boolean; // This is where filtering happens
property Current: TMyObject;
end;
and just return self:
function TFilteredEnum.GetEnumerator: TSimpleEnumerator;
begin
result := Self;
end;
and Delphi will conveniently clean up your instance for you, just like it does any other enumerator:
var
L: TMyList;
E: TMyObject;
begin
for E in TFilteredEnum.Create(L, 7) do ;
end;
You can then extend your enumerator to use an anonymous method, which you can pass in the constructor:
TFilterFunction = reference to function (AObject: TMyObject): boolean;
TFilteredEnum = class
private
FFilterFunction: TFilterFunction;
public
constructor Create(AList:TList<TMyObject>; AFilterFunction: TFilterFunction);
...
end;
...
function TFilteredEnum.MoveNext: boolean;
begin
if FIndex >= FList.Count then
Exit(False);
inc(FIndex);
while (FIndex < FList.Count) and not FFilterFunction(FList[FIndex]) do
inc(FIndex);
result := FIndex < FList.Count;
end;
call it like this:
var
L:TMyList;
E:TMyObject;
begin
for E in TFilteredEnum.Create(L, function (AObject: TMyObject): boolean
begin
result := AObject.Value = 7;
end;
) do
begin
//do stuff here
end
end;
Then you could even make it a generic, but I wont do that here, my answer is long enough as it is.
N#
I use this approach...where the AProc performs the filter test.
TForEachDataItemProc = reference to procedure ( ADataItem: TDataItem; var AFinished: boolean );
procedure TDataItems.ForEachDataItem(AProc: TForEachDataItemProc);
var
AFinished: Boolean;
ADataItem: TDataItem;
begin
AFinished:= False;
for ADataItem in FItems.Values do
begin
AProc( ADataItem, AFinished );
if AFinished then
Break;
end;
end;