Delphi trouble: Sorting a Tobjectlist<> - delphi

I want to sort my generic tobjectlist using the built-in sort method.
here is what I do:
//create the list object
myList := TObjectList<MyType>.Create(false);
[...] //populate the list with unsorted entries
//sort the list
myList.sort(#Comparer);
[...]//store sorted results back to array
myList.Destroy;
my Comparer function looks like this:
function Comparer(Item1, Item2 : pointer):integer;
begin
result := myCompare(item1, item2);
end;
According to the specs, it should work like this.
I get an compiler error E2250 No overloaded version of 'Sort' exist with these parameters (exact wording differs, i use a non english version of RAD Studio)
I have no idea why this should not be valid Pascal - does anyone of you have insight to share on this?

You are almost there. Since I don't know what MyType is you may need to change the call to your myCompare function.
myList.Sort(TComparer<MyType>.Construct(
function (const L, R: MyType): integer
begin
result := myCompare(L, R);
end
));

TObjectList<T>.Sort is declared as:
procedure Sort(const AComparer: IComparer<T>);
IComparer<T> is defined as:
IComparer<T> = interface
function Compare(const Left, Right: T): Integer;
end;
You are instantiating TObjectList<MyType> and so you need to pass an IComparer<MyType> to Sort. In order to do this you will need an object to provide a concrete implementation of that interface.
One obvious way to do this would be to subclass TObjectList<MyType> and implement the interface there.
Another way to do this is to use TComparer<T> to create an IComparer<T> on demand using its Construct class function. You would need to supply a comparison function:
TComparison<T> = reference to function(const Left, Right: T): Integer;
Leonardo's answer demonstrates how to do this.

If the compiler says no overloaded version exists with that parameter type, ask yourself what overloads do exist. Check the source code or the documentation to find out.
There you'll see that TObjectList<T> inherits two Sort methods from TList<T>. One takes no arguments, and the other takes a reference to something implementing the IComparer<T> interface. Your standalone function doesn't fit that. Write a descendant of TComparer<MyType> and override its Compare method.

Related

How do I assign a pre-existing function to a TComparison<T>?

program Project55;
{$APPTYPE CONSOLE}
uses
System.Generics.Defaults;
type
TestRec<T> = record
Compare: TComparison<T>;
CompareI: IComparer<T>;
end;
var
TRI: TestRec<Integer>;
begin
TRI.CompareI:= TComparer<Integer>.Default;
TRI.Compare:= TRI.CompareI.Compare; //E2035 Not enough actual parameters
TRI.Compare:= #TRI.CompareI.Compare; //E2035 Not enough actual parameters
end.
I know I can assign the function body as an anonymous function, but why can't I assign an existing function?
Of course the following works, but that's just silly:
TRI.Compare:= function(const L,R: integer): Integer
begin
Result:= TRI.CompareI.Compare(L,R);
end;
PS. I'm using Delphi XE7, but I doubt the version matters.
Knowing that IComparer<T> is an interface with just one method that has the same signature as TComparison<T> and that anonymous methods are just interfaces with one method you can do the following.
IComparer<Integer>(TRI.Compare) := TRI.CompareI;
I am using that trick in Spring4D to avoid creating a wrapper object around a TComparison<T> to be passed as IComparer<T> because they are binary compatible.
Your attempts to perform this assignment fail because an interface method cannot be with assigned to a method reference variable. The language simply does not permit that. The types are not assignment compatible. Valid assignment sources are anonymous methods, methods of classes (instance or class) and unit scope procedures.
The tricks that can be seen in other answers all depend on in depth knowledge of the implementation details. Which means that they are subject to change. But in terms of the language, what you are attempting is not permitted.
Anonymous methods are not exactly method pointers. They are implemented as an interface with a single method "Invoke".
It is possible to extract a method pointer from an anonymous method, but as far as I know it relies on the current implementation details of anonymous method and could be subject to changes in future version of delphi. In other words, I would advise against it. This was taken verbatim from Barry Kelly's post here. (Which covers the topic more thoroughly than I do here)
procedure MethRefToMethPtr(const MethRef; var MethPtr);
type
TVtable = array[0..3] of Pointer;
PVtable = ^TVtable;
PPVtable = ^PVtable;
begin
// 3 is offset of Invoke, after QI, AddRef, Release
TMethod(MethPtr).Code := PPVtable(MethRef)^^[3];
TMethod(MethPtr).Data := Pointer(MethRef);
end;
Based on your example, I'd propose this as an alternative
type
TestRec<T> = record
CompareI: IComparer<T>;
function Compare(const L, R : T) : Integer;
end;
[...]
function TestRec<T>.Compare(const L, R : T) : Integer;
begin
Result := CompareI.Compare(L,R);
end;
But then, it may/may not apply to your current situation.

Implementing Custom Binary Search for TObjectList<myClass> (Delphi XE)

I need to implement a binary search on TObjectList that uses a custom comparer, I believe using TCustomComparer.
Goal: binary search returns instances in the list that conform to a particular property parameter.
For example:
TMyClass=class
public
Index:integer
end;
TMyObjectList=TObjectList<TMyClass>;
begin
...
aMyClass.Index:=1;
aMyObjectList.binarysearch(aMyClass, aMyClassRef)
...
end;
Or simply:
begin
...
aMyObjectList.binarysearch(1, aMyClassRef)
...
end;
I want to loop and get back instances of TMyClass in the list that also have Index==1.
In C++, overloading the '==' operator achieves this goal.
The new Delphi 'help' is rather sparse and scattered around making things hard to find, and I'm not that familiar with all the nuances of the new Delphi generics.
So - how do I do it in Delphi XE with the Generics.TObjectList?
(Using Delphi XE).
TIA
The help file is indeed a little limited here. I generally just read the source code to Generics.Defaults and Generics.Collections. Anyway, you need to provide an IComparer<TMyClass>. There's lots of ways to do that. For example, using an anonymous function:
var
List: TObjectList<TMyClass>;
Target: TMyClass;
Index: Integer;
Comparer: IComparer<TMyClass>;
Comparison: TComparison<TMyClass>;
....
Comparison :=
function(const Left, Right: TMyClass): Integer
begin
//Result := ??;//your comparison rule goes here
end;
Comparer := TComparer<TMyClass>.Construct(Comparison);
List.BinarySearch(Target, Index, Comparer);
If you don't want to use an anonymous function you can implement Comparison some other way. For example a method of some object, or a class function, or even just a plain old fashioned non-OOP function. It just has to have the same signature as above.
As always with comparison functions, return <0 if Left<Right, >0 if Left>Right and 0 if Left=Right.

Calling member functions dynamically

I'm pretty sure it's possible to call a class and its member function dynamically in Delphi, but I can't quite seem to make it work. What am I missing?
// Here's a list of classes (some code removed for clarity)
moClassList : TList;
moClassList.Add( TClassA );
moClassList.Add( TClassB );
// Here is where I want to call an object's member function if the
// object's class is in the list:
for i := 0 to moClassList.Count - 1 do
if oObject is TClass(moClassList[i]) then
with oObject as TClass(moClassList[i]) do
Foo();
I get an undeclared identifier for Foo() at compile.
Clarification/Additional Information:
What I'm trying to accomplish is to create a Change Notification system between business classes. Class A registers to be notified of changes in Class B, and the system stores a mapping of Class A -> Class B. Then, when a Class B object changes, the system will call a A.Foo() to process the change. I'd like the notification system to not require any hard-coded classes if possible. There will always be a Foo() for any class that registers for notification.
Maybe this can't be done or there's a completely different and better approach to my problem.
By the way, this is not exactly an "Observer" design pattern because it's not dealing with objects in memory. Managing changes between related persistent data seems like a standard problem to be solved, but I've not found very much discussion about it.
Again, any assistance would be greatly appreciated.
Jeff
First of all you're doing something very unusual with TList: TList is a list of UNTYPED POINTERS. You can add any pointer you want to that list, and when you're doing moClassList.Add( TClassA ) you're actually adding a reference to the class TClassA to the list. Technically that's not wrong, it's just very unusual: I'd expect to see TClassList if you actually want to add a class! Or TList<TClass> if you're using a Delphi version that support it.
Then you're looping over the content of the list, and you're checking if oObject is of the type in the list. So you do want classes in that list after all. The test will work properly and test rather the object is of that type, but then when you do with oObject as TClass(moClassList[i]) do you're actually casting the object to... TObject. Not what you wanted, I'm sure!
And here you have an other problem: Using Foo() in that context will probably not work. TObject doesn't contain a Foo() method, but an other Foo() method might be available in context: That's the problem with the with keyword!
And to finally answer the question in the title bar: Delphi is not an Dynamic language. The compiler can't call a method it doesn't know about at compile time. You'll need to find a OOP way of expressing what you want (using simple inheritance or interfaces), or you may call the function using RTTI.
Edited after question clarification.
All your business classes need to implement some kind of notification request management, so your design benefits allot from a base class. Declare a base class that implements all you need, then derive all your business classes from it:
TBusinessBase = class
public
procedure RegisterNotification(...);
procedure UnregisterNotification(...);
procedure Foo;virtual;abstract;
end;
In your initial example you'd no longer need the list of supported classes. You'll simply do:
oObject.Foo;
No need for type testing since Delphi is strongly typed. No need for casting since you can declare oObject": TBusinessBase.
Alternatively, if you for some reason you can't change the inheritance for all your objects, you can use interfaces.
TClass is defined:
TClass = class of TObject;
You then write oObject as TClass which is effectively a null operation since oObject already was a TObject.
What you need is something like this:
type
TFoo = class
procedure Foo();
end;
TFooClass = class of TFoo;
TBar = class(TFoo)
procedure Bar();
end;
....
if oObject is TFooClass(moClassList[i]) then
with oObject as TFooClass(moClassList[i]) do
Foo();
This explains why your attempts to call Foo() does not compile, but I simply have no idea what you are trying to achieve. Even after your clarification I'm struggling to understand the problem.
Here's a really contrived example (using an array instead of a TList) that I think is what you're trying to do (error handling and try..finally intentionally omitted for clarity).
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TBaseClass=class(TObject)
procedure Foo; virtual;
end;
TClassA=class(TBaseClass)
procedure Foo; override;
end;
TClassB=class(TBaseClass)
procedure Foo; override;
end;
TClassArray= array of TBaseClass;
{ TClassB }
procedure TClassB.Foo;
begin
Writeln('TClassB.Foo() called.');
end;
{ TClassA }
procedure TClassA.Foo;
begin
Writeln('TClassA.Foo() called.');
end;
var
Base: TBaseClass;
ClassArr: TClassArray;
{ TBaseClass }
procedure TBaseClass.Foo;
begin
Writeln('TBaseClass.Foo called!!!!!!!!');
end;
begin
ClassArr := TClassArray.Create(TClassA.Create, TClassB.Create);
for Base in ClassArr do
Base.Foo;
for Base in ClassArr do
Base.Free;
ReadLn;
end.

Delphi: how to use TObjectList<T>?

I need to understand how to use the generic Delphi 2009 TObjectList. My non-TObjectList attempt looked like
TSomeClass = class(TObject)
private
FList1: Array of TList1;
FList2: Array of TList2;
public
procedure FillArray(var List: Array of TList1; Source: TSource); Overload;
procedure FillArray(var List: Array of TList2; Source: TSource); Overload;
end;
Here, TList1 and TList2 inherits the same constructor constructor TParent.Create(Key: string; Value: string);. However, due to different specialization (e.g. different private fields), they will not be of the same type. So I have to write two nearly identical fill methods:
procedure TSomeClass.FillArray(var List: Array of TList1; Source: TSource);
begin
for i := 0 to Source.List1.Count - 1 do begin
SetLength(List, Length(List) + 1);
List[i] := TList1.Create(Source.List1[i].Key, Source.List1[i].Value);
end;
end;
with FillArray(List: Array of TList2; Source: TSource); being identical, except for the replacement of TList1 with TList2 throughout. As far as I understand, this could be neatly circumvented by using TObjectList and a single fill method; yet, I don't have a clue how to go about this. Do anyone have some good pointers on this? Thanks!
You wouldn't be able to condense that down by using a generic list, since a generic's type is part of the class definition. So a TObjectList<TMyClass1> is different from (and incompatible with) a TObjectList<TMyClass2>. The main benefit of using generic lists over normal TList/TObjectList is improved type safety, with less casts and cleaner code.
Also, if you're using key/value pairs, are you putting them into a list and then retrieving them by searching for a key and returning the associated value? If so, take a look at TDictionary in Generics.Collections. It's a generic key/value hash table that will greatly simplify this process for you.
The Official Embarcadero documentation Wiki on the Generics.Collections.TObjectList contains a simple code example of the TObjectList in action.
I'm not certain exactly what the question is driving at but to address the broad use of a TObjectList, the example initialisation code for a TObjectList might look like this:
var
List: TObjectList<TNewObject>;
Obj: TNewObject;
begin
{ Create a new List. }
List := TObjectList<TNewObject>.Create();
{ Add some items to the List. }
List.Add(TNewObject.Create('One'));
List.Add(TNewObject.Create('Two'));
{ Add a new item, but keep the reference. }
Obj := TNewObject.Create('Three');
List.Add(Obj);
The example code should give you an idea of what the TObjectList can do but If I've understood the question correctly it seems that you would like to be able to add more than one class type to a single instance of the TObjectList? A TObjectList can only be initiated with a single type so it might be better if you initiated the TObjectList with a Interface or Abstract class that is shared by all of the classes you wish to add to it.
One important difference when using a TObjectList compared to creating your own is the existance of the OwnsObjects property which tells the TObjectList whether it owns the objects you add to it and therefore consequently whether it should manage freeing them itself.
Something like this?
TSomeClass = class
private
FList1: TArray<TList1>;
FList2: TArray<TList2>;
public
procedure FillArray<T>(var List: TArray<T>; Source: TSource);
end;
procedure TSomeClass.FillArray<T>(var List: TArray<T>; Source: TSource);
begin
for i := 0 to Source.List1.Count - 1 do begin
SetLength(List, Length(List) + 1);
List[i] := T.Create(Source.List1[i].Key, Source.List1[i].Value);
end;
end;
This, or something like it should do what you want, afaict.

need help with interesting call to JCL's TEvaluator

i'm using JCL's expression evaluator TEvaluator (a marvelous creation donated by barry kelly). (THANK YOU barry!)
background
i've used the AddFunc method.
function MyFunc:double;
begin
// calculations here
Result:=1;
end;
you can use the AddFunc method to make the function available:
AddFunc('MyFunc', MyFunc);
here's the problem...
i need to call a method on an object instead of a standalone routine.
the reason is that i have a list of objects that provide the values.
say we have a list of vehicle objects. each object has a Weight function. i want to be able to make available each object's weight available for use in the formula.
a silly example but it's easy to explain:
type
TVehicle=class
private
public
function Weight:double;
end;
function StrangeCalculation:double;
var
vehicle:TVehicle;
begin
for iVehicle = 0 to Count - 1 do
begin
vehicle:=GetVehicle(iVehicle);
// E2250 There is no overloaded version of 'AddFunc' that can be called with these arguments
eval.AddFunc(vehicle.Name, vehicle.Weight);
end;
Result:=eval.Evaluate('JeepTJWeight + FordF150Weight * 2');
end;
my options:
AddVar( ) or AddConst( ) -- but that isn't so great because i need to be able to raise an exception if the value is not available.
AddFunc( ) with standalone functions. can't do that because the names of (and number of) variables is unknown until runtime.
modify the object to add a callback if the variable isn't found. i have actually done this but needed to edit a copy of the source to call back to make it do this.
make an AddFunc( ) that's able to use method functions.
option #3 is actually built but an additional AddFunc would be nicer. the trouble is i don't know what method prototype to provide. i thought TMethod would be the way but my knowledge is too limited here... here was my unsuccessful attempt but i still get "E2250 There is no overloaded version of 'AddFunc' that can be called with these arguments" at the eval.AddFunc() call like before.
TFloat64MethodFunc = function(c:pointer): TFloat64;
procedure TEasyEvaluator.AddFunc(const AName: string; AFunc: TFloat64MethodFunc);
begin
FOwnContext.Add(TExprFloat64MethodFuncSym.Create(AName, AFunc));
end;
TExprFloat64MethodFuncSym = class(TExprAbstractFuncSym)
private
FFunc: TFloat64MethodFunc;
public
constructor Create(const AIdent: string; AFunc: TFloat64MethodFunc);
function Evaluate: TFloat; override;
// not using function Compile: TExprNode; override;
end;
thank you for your help!
mp
figured it out...
TFloat64MethodFunc = function: TFloat of object;
Long time ago (2004), I have faced this problem. My solution then was to use the Turbo Power SysTools evaluator, that accepts methods.

Resources