A list class to store enums? - delphi

What List type I should use to store enum values? I have tried with TObjectList, I cast to TObject to Add the value, but can't cast it back to enum when reading from the list.
What list do you use to store enums?

Casting enums to Pointer or TObject and back works just fine. If your Delphi version supports generics use Tim's suggestion, it's better. Alternatively you can use an dynamic array (array of TTestEnum) or create a wrapper class around the dynamic array - that's how generic lists are implemented in Delphi versions capable of generics.
Here's a quick console demo, using TList, not TObjectList because TList makes fewer assumptions about the items it holds.
program Project1;
{$APPTYPE CONSOLE}
uses SysUtils, Classes;
type TTestEnum = (enum1, enum2, enum3, enum4);
var L: TList;
i: Integer;
E: TTestEnum;
begin
L := TList.Create;
try
L.Add(Pointer(enum1));
L.Add(Pointer(enum2));
L.Add(Pointer(enum3));
L.Add(Pointer(enum4));
for i:=0 to L.Count-1 do
begin
E := TTestEnum(L[i]);
case E of
enum1: WriteLn('enum1');
enum2: WriteLn('enum2');
enum3: WriteLn('enum3');
enum4: WriteLn('enum4');
end;
end;
finally L.Free;
end;
ReadLn;
end.

Could you not just use Generics for this?
TList<TEnumName>;

This answer may help. It's about storing records in a TList by creating a descendant to avoid all the typecasting. Note that you won't need to worry about allocating/freeing memory for the enum values, as they're simple ordinal types that fit in the space of a pointer.
Note that you have to typecast to Pointer when Adding to the list, and may have to typecast as `YourEnum(Integer(List[Index])) when reading back. However, the code I linked to shows how to handle both in the descendant class so it's only done once each way, and that's buried in the class implementation.

Related

TValue string<-->Boolean back and forth

I'm playing arround with TValue
I've written this code in a blank project:
uses
RTTI;
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
b: Boolean;
begin
s := TValue.From<Boolean > (True).ToString;
b := TValue.From<string > (s).AsType<Boolean>;
end;
But I can not convert back from string to boolean; I get an Invalid Typecast exception in the second line.
I'm using Delphi XE but it is the same result in Delphi Xe6 which leads me to the conclusion: I'm using TValue wrong.
So please what am I doing wrong.
Although you give Boolean as the example in your question, I'm going to assume that you are really interested in the full generality of enumerated types. Otherwise you would just call StrToBool.
TValue is not designed to perform the conversion that you are attempting. Ultimately, at the low-level, the functions GetEnumValue and GetEnumName in the System.TypInfo unit are the functions that perform these conversions.
In modern versions of Delphi you can use TRttiEnumerationType to convert from text to an enumerated type value:
b := TRttiEnumerationType.GetValue<Boolean>(s);
You can move in the other direction like this:
s := TRttiEnumerationType.GetName<Boolean>(b);
These methods are implemented with calls to GetEnumValue and GetEnumName respectively.
Older versions of Delphi hide TRttiEnumerationType.GetValue and TRttiEnumerationType.GetName as private methods. If you are using such a version of Delphi then you should use GetEnumName.
TValue is not meant to convert types that are not assignment compatible. It was designed to hold values while transporting them in the RTTI and to respect the assignment rules of Delphi.
Only ToString can output the value in some string representation but a type that you cannot simply assign a string to will also fail when doing that with TValue.
TValue is not a Variant.
If you want to convert a string to boolean and back then use StrToBool and BoolToStr.

store array of TPoint inside TObjectList

I defined a Objectlist to store several Polygons as TFPolygon = array of TPoint inside this TObjectList; but with the add function of my objectlist I get an Access violation error
:
type
TFPolygon = array of TPoint;
TFPolygonList = class(TObjectList)
private
procedure SetPolygon(Index: Integer; Value: TFPolygon);
function GetPolygon(Index: Integer): TFPolygon;
public
procedure Add(p: TFPolygon);
property Items[index: Integer]: TFPolygon read GetPolygon write SetPolygon; default;
end;
implementation
procedure TFPolygonList.SetPolygon(Index: Integer; Value: TFPolygon);
begin
inherited Items[Index] := Pointer(Value);
end;
function TFPolygonList.GetPolygon(Index: Integer): TFPolygon;
begin
Result := TFPolygon(inherited Items[Index]);
end;
procedure TFPolygonList.Add(p: TFPolygon);
begin
inherited Add(Pointer(p));
end;
I can not understand the error inside this code sample ? Can I only store classes inside a TObjectList or is my approach to store arrays of TPoints also valid ?
Your approach is not valid. Dynamic arrays are managed types. Their lifetimes are managed by the compiler. For that to work you must not cast away the fact that they are managed types, which is exactly what you did.
You cast the dynamic array to Pointer. At that point you have taken a new reference to the dynamic array, but the compiler is not aware of it because a Pointer is not a managed type.
You've got a few options to solve your problem.
If you are on a modern Delphi then stop using TObjectList. Instead use the generic type safe containers in Generics.Collections. In your case TList<TFPolygon> is what you need. Because this is compile time type safe, all the lifetime of the managed types is taken care of.
If you are on an older Delphi, then you can wrap your dynamic array inside a class. Then add instances of those classes to your TObjectList. Make sure that your list is configured to own its objects. It's perfectly possible for you to do that wrapping purely in the implementation of TFPolygonList which will encapsulate things well.

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.

Is there a compile time flag to guard against accessing objects from StringList without using "objects"

I can't count how many times I may have erroneously done this:
for i := 0 to MyList.count-1 do begin
myobject := TMyObject(MyList[i])
.......
end;
when it should be this:
for i := 0 to MyList.count-1 do begin
myobject := TMyObject(MyList.objects[i])
.......
end;
Note the objects in the second snippet of code.
The erroneous snippet of code will run, and will obviously throw an access violation when I try to make use of myobject. But it isn't always apparent what it is I am doing wrong.
Is there a compiler option which will guard against this?
Delphi has 2 ways to typecast, hardcast using TObject(var) and a softcast using the As operator.
It is a good practice to always use the As operator unless you are 100% sure.
In your specific example
(MyList[i]) as TMyObject does not compile
where as
(MyList.objects[i]) as TMyObject does.
No. The compiler assumes when you type-cast that you know what you're doing. It will allow pretty much any type-cast where the two types are the same size, with the notable exception of casting between integral and floating-point types.
For this particular instance, try getting yourself into the habit of using the as operator instead. Then the compiler will catch you when you forget to use the Objects property:
myobject := MyList[i] as TMyObject; // compiler error
myobject := MyList.Objects[i] as TMyObject; // OK
It looks like you are using a TStringList to hold a string/object pair. If you are using Delphi 2009 or later an alternative suggestion is to replace your TStringList with a generic TDictionary like so:
uses
Generics.Collections;
...
MyDictionary: TDictionary<String, TMyObject>;
...
for MyObject in MyDictionary.Values do
begin
...
end;
Then the entire operation is type safe and you won't need to cast at all.
Note: You can't reassign the iteration variable inside a for..in loop (but you can call its methods and change the value of its properties.

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.

Resources