tags:

views:

134

answers:

5

I'm trying to store a set inside the object property (and read it) of a TStringList (I will also use it to store text associated to the set) but I get a invalid typecast for the set.

What's the best way to store a set inside a StringList object? Also, will this object need to be freed when destroying the StringList?

Here's some example code:

type
 TDummy = (dOne, dTwo, dThree);
 TDummySet = set of TDummy;


var
  DummySet: TDummySet;
  SL: TStringList;
begin
  SL := TStringList.Create;
  Try
    DummySet := [dOne, dThree];
    SL.AddObject('some string', TObject(DummySet)); // Doesn't work. Invalid typecast
  Finally
    SL.Free;
  End;
end;
A: 

I don't think a stringlist is the way to go. Why not an array of TDummySet? And no, there is no need to free it because the set is not an object.

var
  Test: Array of TDummySet;

SetLength(Test, 2);
Test[0] := [dOne, dThree];
Test[1] := [dTwo];

When you're done:

SetLength(Test, 0);
johnny
Thanks for the reply. I'll also be needing to associate a string to each of the sets, would a record (name: string; Test: TDummySet;) then be a better option than a TStringList? The inital idea to use a TStringList is because I will need to find items inside the list to check for duplicates.
smartins
I'm guessing now, but if you want to use a stringlist to find your set with indexof, i.e. 'some string' is some kind of key, you can create your own object as a wrapper for your set.
johnny
I would use a stringlist and objects, but I don't know if it's the best way to go. Then you can sort the stringlist to find duplicates or set Duplicates to dupError/dupIgnore, raising an exception or ignoring when attempting to add duplicates.
johnny
A: 

You cannot make a typecast from your set to a TObject, because your variable is not a pointer.

You have to store a pointer to your variable in the TStringList. In that case, you'll have to allocate and deallocate it manually too.

Try something like this:

type
  TEnum = (one, two, three);
  TSet = set of TEnum;
  PSet = ^TSet;
var s: TStringList;
    p: PSet;
begin
  s := TStringList.Create;
  p := AllocMem(SizeOf(TSet));
  p^ := [two, three];
  S.AddObject('a', TObject(p));

  // bla bla bla

  // Here you read the set in the string list
  if (two in PSet(S.Objects[0])^)) then begin
    // your checks here
  end

  ...
Doug
Thanks for your reply. Can I set OwnsObjects := True so that Objects are automatically freed by the list or should I manually free them?
smartins
No, because the items are not objects (TObject instances). You have to manually free them - in the above example, use FreeMem.
TOndrej
this is a 'brute force' solution ...
mjustin
I'd prefer New to AllocMem - it's better "integrated" into the language.
Ulrich Gerhardt
The first sentence is wrong. You can type-cast non-pointer variables to TObject all the time. The issue is that the set is too small to be cast, and the compiler has no way of expanding it to fit. If you cast to Byte first, as Ulrich's answer demonstrates, then it's still too small, but the compiler knows how to construct a four-byte numeric type from a one-byte numeric type (and vice versa).
Rob Kennedy
+4  A: 

I can't add non objects on that case.

What you can do, is create an object that have TDummySet as Field. Something like

TExemple = class
 DummySet = TDummySet;
end;

Or you can use a different approach:

Declarations:

  TDummy = (dOne, dTwo, dThree);
  TDummySet = set of TDummy;
  PDummySet = ^TDummySet;

How to use:

var
  DummySet: PDummySet;
 begin
  New(DummySet);
  DummySet^ := [dOne, dThree];
SaCi
Doug posted while I was writing, anyway I leave it here.
SaCi
+1 your suggestion to use a 'wrapper' object is imho *far* better
mjustin
+2  A: 

You should not store a set via TStringList.Objects because what Objects use (TObject) is a 32 bit value type and sets can be represented up to 256 bits depending on the size of the set. That's probably why the compiler doesn't even allow casting.

A better way to serialize sets is using RTTI. I am not sure where VCL exposes its builtin set serialization mechanism but JCL has a JclRTTI unit with JclSetToStr and JclStrToSet functions.

var 
  fs: TFontStyles;
begin 
  JclStrToSet(TypeInfo(TFontStyles), fs, 'fsBold, fsItalic'); // from string
  Showessage(JclSetToStr(TypeInfo(TFontStyles), fs));         // to string  
end;
utku_karatas
Thanks for the reply. Unfortunately I will also need to use the string part for other data so converting the set to string won't probably be the best option. But thanks for the pointer.
smartins
+4  A: 

First read the other answers - probably you'll find a less hacky solution.

But FTR: You can write

SL.AddObject('some string', TObject(Byte(DummySet)));

and

DummySet := TDummySet(Byte(SL.Objects[0]));

if you really want.

Note: You'll have to change the keyword Byte if you add enough elements to the TDummySet type. For example, if you add six more elements (so that there is a total of nine) you need to cast to Word.

Ulrich Gerhardt
Hard casts to T<SomeClass> are *evil* ... (q_q)
mjustin
@mjustin: I suppose by hard cast you mean `TObject(...)` instead of `... as TObject`? The latter doesn't make much sense if casting a byte, does it? :-)
Ulrich Gerhardt
Yes, casting a Byte to TObject is nonsense :P
mjustin
No, casting a Byte **using `as`** is nonsense. But casts per se sometimes have their place. And this might be one: Whether you use TStringList directly or write a descendant, you'll need some casts.
Ulrich Gerhardt