如何将一个集合存储在TStringList对象中?

4
我正在试图将一个集合存储在TStringList对象属性中(并读取它),但是我得到了一个无效的类型转换错误。我还将使用它来存储与该集合相关的文本。请问,如何最好地将一个集合存储在StringList对象中?另外,在销毁StringList时是否需要释放该对象?下面是一些示例代码:
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;

如果您只是陈述问题,可能会得到更好的答案 - 也许有一个比TStringList更好的类(或不同的数据结构)可供使用?但是为此,您需要告诉我们您想要做什么并寻求最佳解决方案。例如:是否会有重复的字符串和/或集合?您是否需要按字符串和/或集合进行键控访问,并具有什么特征? - mghie
以下是我问题的详细描述。我需要一个字符串列表,每个字符串都有一个关联的集合。重复项不会被添加,而是更新重复文本的集合以反映文本所存在的集合。使用带有关联集合对象的TStringList是实现这一目标的最佳方式吗? - smartins
3
如果您使用的是D2009+,我建议您看一下TDictionary。 - Uli Gerhardt
5个回答

5

首先请阅读其他答案——也许您会找到一个不那么hacky的解决方案。

但是,如果需要,您可以编写以下内容:

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

并且

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

如果您真的想要的话。
注意:如果您向TDummySet类型添加足够的元素,则需要更改关键字“Byte”。例如,如果您再添加六个元素(总共九个),则需要强制转换为“Word”。

强制转换到T <SomeClass>是邪恶的...(q_q) - mjn
@mjustin:我想你所说的硬转换是指TObject(...)而不是... as TObject吧?如果转换一个字节,后者并没有太多意义,对吧? :-) - Uli Gerhardt
是的,将Byte强制转换为TObject是无意义的:P - mjn
2
不,使用 as 进行 Byte 强制转换是无意义的。但是强制转换有时确实有其用处。这可能是其中之一:无论您直接使用 TStringList 还是编写一个派生类,都需要进行一些强制转换。 - Uli Gerhardt

4

我不能在那种情况下添加非对象。

你可以做的是创建一个具有TDummySet字段的对象。类似这样的:

TExemple = class
 DummySet = TDummySet;
end;

或者您可以采用不同的方法:

声明:

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

使用方法:

var
  DummySet: PDummySet;
 begin
  New(DummySet);
  DummySet^ := [dOne, dThree];

当我在写的时候,Doug已经发布了,不管怎样,我还是把它留在这里。 - Rodrigo Farias Rezino
我认为你使用“包装器”对象的建议更好。 - mjn

2

您不应该使用TStringList.Objects来存储一个集合,因为Objects使用的是32位值类型TObject,而集合可以根据其大小表示多达256位。这可能就是编译器甚至不允许转换的原因。

序列化集合的更好方法是使用RTTI。我不确定VCL在哪里公开其内置的集合序列化机制,但JCL有一个JclRTTI单元,其中包含JclSetToStr和JclStrToSet函数。

var 
  fs: TFontStyles;
begin 
  JclStrToSet(TypeInfo(TFontStyles), fs, 'fsBold, fsItalic'); // from string
  Showessage(JclSetToStr(TypeInfo(TFontStyles), fs));         // to string  
end;

谢谢您的回复。不幸的是,我还需要使用字符串部分来处理其他数据,因此将集合转换为字符串可能不是最佳选择。但是感谢您的指引。 - smartins

0

我认为字符串列表不是最好的选择。为什么不使用TDummySet数组呢?而且,不需要释放它,因为集合不是一个对象。

var
  Test: Array of TDummySet;

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

当你完成时:

SetLength(Test, 0);

谢谢回复。我还需要将一个字符串与每个集合关联起来,使用记录(名称:字符串; 测试:TDummySet;) 是否比TStringList更好?最初使用TStringList的想法是因为我需要在列表中查找项目以检查重复项。 - smartins
现在我只是猜测,但如果你想使用stringlist通过indexof来查找你的集合,也就是“一些字符串”是某种键,那么你可以创建一个自己的对象作为你集合的包装器。 - johnny
1
我会使用字符串列表和对象,但我不确定这是否是最好的方法。然后,您可以对字符串列表进行排序以查找重复项,或将Duplicates设置为dupError / dupIgnore,在尝试添加重复项时引发异常或忽略它们。 - johnny

0

你不能将你的集合强制转换为TObject,因为你的变量不是指针。

你必须在TStringList中存储一个指向你的变量的指针。在这种情况下,你也需要手动分配和释放它。

尝试像这样:

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

  ...

谢谢您的回复。我可以将 OwnsObjects 设置为 True,以便列表自动释放对象,还是我应该手动释放它们? - smartins
不可以,因为这些项不是对象(TObject实例)。你必须手动释放它们 - 在上面的示例中,使用FreeMem。 - Ondrej Kelle
1
我更喜欢New而不是AllocMem——它在语言中更好地“集成”了。 - Uli Gerhardt
1
第一句话是错误的。你可以随时将非指针变量强制转换为TObject。问题在于集合太小,无法进行转换,编译器也无法扩展它以适应。如果像Ulrich的答案所示先转换为Byte,那么仍然太小,但编译器知道如何从一个字节大小的数值类型构造一个四字节大小的数值类型(反之亦然)。 - Rob Kennedy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接