如何保存/加载类型集?

时间:2012-03-04 08:40:10

标签: delphi types typecast-operator

我有这段代码

type
  TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType6, xsTyp7, xsType8); // up to FXSample30;  
..

private
  FXSample = Set of TXSample;  
..

published
  property Sample: TXSample read FXSample  write FXSample; 
..

  //if Sample has a value of
  Sample := [xsType2, xsType4, xsType5, xsType6, xsTyp7];

如何保存/加载Sample的属性?
我想将它保存在数据库中 有可能吗?

9 个答案:

答案 0 :(得分:25)

如果您的集合永远不会超过32种可能性(Ord(High(TXSample)) <= 31),那么将该集合强制转换为Integer并返回:

是完全可以的。
type
  TXSamples = set of TXSample;
var 
  XSamples: TXSamples;
begin
  ValueToStoreInDB := Integer(XSamples);
  Integer(XSamples) := ValueReadFromDB;
end;

更具体地说:SizeOf(TXSamples)必须精确等于SizeOf(StorageTypeForDB)。因此,在将Ord(High(TXSample))强制转换为:

时,以下范围适用于TXSamples
  • Byte: Ord(High(TXSample)) < 8
  • Word: 8 <= Ord(High(TXSample)) < 16
  • Longword: 16 <= Ord(High(TXSample)) < 32
  • UInt64: 32 <= Ord(High(TXSample)) < 64

答案 1 :(得分:6)

Delphi中无法直接对类变量进行类型转换,但内部Delphi将该集存储为字节值。通过使用无类型移动,可以很容易地将其复制到整数中。请注意,这些函数最多只能达到32(整数的边界)。要增加界限,请改用 Int64

function SetToInt(const aSet;const Size:integer):integer;
begin
  Result := 0;
  Move(aSet, Result, Size);
end;

procedure IntToSet(const Value:integer;var aSet;const Size:integer);
begin
  Move(Value, aSet, Size);
end;

演示

type
  TMySet = set of (mssOne, mssTwo, mssThree, mssTwelve=12);
var
  mSet: TMySet;
  aValue:integer;
begin
  IntToSet(7,mSet,SizeOf(mSet));
  Include(mSet,mssTwelve);
  aValue := SetToInt(mSet, SizeOf(mSet));
end;

答案 2 :(得分:4)

Delphi集只是(可能)相关布尔标志的集合。每个布尔标志对应于匹配的序数值是否在集合中。

通过将集合表示为bitset,您当然可以将集合打包为整数值。或者您可以创建该集的文本表示。

但是,这两个选项都使您无法在SQL级别查询数据库。出于这个原因,我建议你将集合中的每个值(即每个布尔标志)表示为数据库表的单独字段(即列)。这为您提供了最强大的数据表示。

答案 3 :(得分:3)

在数据库中存储集合的最简单方法(如注释中提到的@DavidHeffernan)是将您的集转换为位掩码。 在int32(整数)值中,您有32位,可以保存set最多32个字段; Delphi在TIntegerSet中定义了SysUtils(参见http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.TIntegerSet)类型。它被声明为:

TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;

所以使用它,将set转换为integer和back(将TIngeterSet转换为整数,反之亦然)很简单;

位掩码也是不错的选择,因为它只是数据库表中的一个INT字段。

你也可以在你的数据库中创建单独的表来存储集合内容(主表(id,...)和setValuesTable(main_id,setElementValue))(这个选项适合在db查询中使用)

以下是使用TIntegerSet

的示例
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;

type
    TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6,  xsType7, xsType8);
    TSampleSet = set of TXSample;



    function SampleSetToInteger(ss : TSampleSet) : integer;
    var intset : TIntegerSet;
        s : TXSample;
    begin
        intSet := [];
        for s in ss do
            include(intSet, ord(s));

        result := integer(intSet);
    end;

    function IntegerToSampleSet(mask : integer) : TSampleSet;
    var intSet : TIntegerSet;
        b : byte;
    begin
        intSet := TIntegerSet(mask);
        result := [];
        for b in intSet do
            include(result, TXSample(b));
    end;

var xs : TSampleSet;
    mask : integer;
begin
    xs := [xsType2, xsType6 .. xsType8];

    mask := SampleSetToInteger(xs);     //integer mask
    xs := IntegerToSampleSet(mask);
end.

答案 4 :(得分:3)

就个人而言,我会将该集转换为整数,并将其作为INT字段存储在数据库中,与其他建议一样。 @teran建议使用TIntegerSet类型,这是我使用位操作处理本机整数的方法。

请注意,您可以使用SampleInInteger()来确定枚举中的某个元素是否存在于SampleSetToInteger()生成的整数掩码中。

以下是代码:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  { .: TXSample :. }
  TXSample = (xsType1 = 0, xsType2, xsType3, xsType4, xsType5,
    xsType6, xsType7, xsType8); // up to FXSample30;
  TXSampleSet = set of TXSample;

// Converts a TXSampleSet to an integer.
function SampleSetToInteger(const S: TXSampleSet): Integer;
var
  Sample: TXSample;
begin
  Result := 0;

  for Sample := Low(TXSample) to High(TXSample) do
    if (Sample in S) then
      Result := Result or (1 shl Ord(Sample));
end;

// Converts an integer to TXSampleSet.
function IntegerToSampleSet(const Int: Integer): TXSampleSet;
var
  I: Integer;
begin
  Result := [];

  for I := 0 to Ord(High(TXSample)) do
    if Int and (1 shl I) <> 0 then
      Result := Result + [TXSample(I)];
end;

// Checks if a TXSample is present in the integer.
function SampleInInteger(const S: TXSample; const Int: Integer): Boolean;
begin
  Result := Int and (1 shl Ord(S)) <> 0;
end;

var
  XSample, XSample1: TXSampleSet;
  Tmp: Integer;
begin
  XSample := [xsType2, xsType4, xsType5, xsType6, xsType7];
  XSample1 := [xsType1];
  Tmp := SampleSetToInteger(XSample);

  Writeln(Tmp);
  XSample1 := IntegerToSampleSet(Tmp);
  if (xsType5 in XSample1) then
    Writeln('Exists');
  if (SampleInInteger(xsType1, Tmp)) then
    Writeln('Exists in int');


  Readln;
end.

答案 5 :(得分:3)

设置变量可以成功保存到TStream后代。这是一个例子。

只需创建一个新的vcl表单应用程序,向其中添加两个TButton组件,并为每个按钮填写OnClick事件,如下例所示。

这是在XE4中创建的,因此对于其他版本的Delphi,uses子句可能会有所不同,但是通过在uses子句中的每个单元之前删除命名空间指示符来进行更改应该是微不足道的。使用铰接值保存集合类型变量可以使用二进制文件,并且可以使用Delphi轻松保存。换句话说,

还建议看一下TypInfo单元,如果你有源或只是使用提供的功能,这使得解析Set类型到它们的文本表示相当简单,虽然这里没有提供示例。如果您想要保存到配置或ini文件或文本可编辑的持久性格式,建议使用此方法。

下面的一个是我所知道的最简单的一个。查看保存到类似下面的流的集合类型的二进制输出意味着它根据其大小保存在集合的最小可能位图表示中。下面的一个映射到磁盘上的一个字节(值为5),这意味着每个值必须映射到2的幂(seThis = 1,seThat = 2,seTheOther = 4),就像手动创建的常量位掩码值一样。编译器可能会强制遵循强制设置保留其常规的规则。这个例子在Delphi XE4中测试过。

希望有所帮助。

布莱恩约瑟夫约翰斯

unit Unit1;

interface

uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
     Vcl.StdCtrls;

type
  TSomeEnum = (seThis, seThat, seTheOther);
  TSomeEnumSet = set of TSomeEnum;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var

  Form1: TForm1;
  SomeSetVar: TSomeEnumSet;
  SomeBoolean: Boolean;
  SomeInt: Integer;

implementation

{$R *.dfm}


procedure TForm1.Button1Click(Sender: TObject);
begin
  SomeSetVar := [seThis, seTheOther];
  SomeBoolean := True;
  SomeInt := 31415;

  with TFileStream.Create('SetSave.bin',fmCreate or fmOpenWrite or fmShareCompat) do
  try
    Write(SomeSetVar,SizeOf(SomeSetVar));
    Write(SomeBoolean,SizeOf(Boolean));
    Write(SomeInt,SizeOf(Integer));
  finally
    Free;
  end;
  SomeSetVar := [];
  SomeInt := 0;
  SomeBoolean := False;

end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ResponseStr: string;
begin
  with TFileStream.Create('SetSave.bin',fmOpenRead or fmShareCompat) do
  try
    Read(SomeSetVar,SizeOf(SomeSetVar));
    Read(SomeBoolean,SizeOf(Boolean));
    Read(SomeInt,SizeOf(Integer));
  finally
    Free;
  end;

  ResponseStr := 'SomeSetVar = ';
  if (seThis in SomeSetVar) then
    ResponseStr := ResponseStr + 'seThis ';

  if (seThat in SomeSetVar) then
    ResponseStr := ResponseStr + 'seThat ';

  if (seTheOther in SomeSetVar) then
    ResponseStr := ResponseStr + 'seTheOther ';

  ResponseStr := ResponseStr + ' SomeBoolean = ' + BoolToStr(SomeBoolean);

  ResponseStr := ResponseStr + ' SomeInt = ' + IntToStr(SomeInt);

  ShowMessage(ResponseStr);

end;

end.

答案 6 :(得分:1)

或者我们可以让编译器完全忘记类型,然后定义它应该看到的内容(如果我们在编译时知道它会看到什么)。这个解决方案非常糟糕,因为它可以只写一行。

type
  // Controls.TCMMouseWheel relies on TShiftState not exceeding 2 bytes in size 
  TShiftState = set of (ssShift, ssAlt, ssCtrl,
                        ssLeft, ssRight, ssMiddle, 
                        ssDouble, ssTouch, ssPen, 
                        ssCommand, ssHorizontal); 

var 
  Shifts : TShiftState;
  Value :  Integer;
begin
  Shifts := TShiftState((Pointer(@Value))^):

  Value  := (PInteger(@Shifts))^;

  if ssShift in TShiftState((Pointer(@Value))^) then 
     Exit;
end;

发生了未使用(顶部)位设置(或不设置)但它对set操作没有影响(in=+,{{ 1}},- ..)。

Delphi中的这一行:

*

在Assembler(Delphi XE6)中是这样的:

Shifts := TShiftState((Pointer(@Value))^);

在Delphi 2007上(lea eax,[ebp-$0c] mov ax,[eax] mov [ebp-$06],ax 较小,可以使用TShiftState)这个汇编程序:

Byte

答案 7 :(得分:0)

您可以使用此单位将set转换为int。如果你需要更多settoint函数,你可以通过查看下面的代码来添加你的。

Set可能只占用1个字节的内存空间。 因此,您可以获得您的Set大小并获得此结果的模块结果。

示例:您的设置大小:您可以获得1个字节的结果 - &gt;

结果:= pINT ^ mod maxVal

您应该通过计算变量类型的maxvalue来获取maxval。

maxVal = Power(2,(8 * sizeof(MySet)-1))

    unit u_tool;

interface
uses Graphics;

type
  TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType6, xsTyp7, xsType8); // up to FXSample30;
  FXSample = Set of TXSample;

  function FXSampleToInt(FXSample: FXSample ): Integer;
  function IntToFXSample(Value: Integer): FXSample;


  function FontStyleToInt(FontStyle: TFontStyles ): Integer;
  function IntToFontStyle(Value: Integer): TFontStyles;

implementation


function FXSampleToInt(FXSample: FXSample ): Integer;
var
  pInt: PInteger;
begin
  pInt := @FXSample;
  Result := pInt^;
end;

function IntToFXSample(Value: Integer): FXSample;
var
  PFXSample: ^FXSample;
begin
  PFXSample := @Value;
  Result := PFXSample^;
end;





function FontStyleToInt(FontStyle: TFontStyles ): Integer;
var
  pInt: PInteger;
begin
  pInt := @FontStyle;
  Result := pInt^;
end;

function IntToFontStyle(Value: Integer): TFontStyles;
var
  PFontStyles: ^TFontStyles;
begin
  PFontStyles := @Value;
  Result := PFontStyles^;
end;






end.

答案 8 :(得分:0)

最简单的解决方案 - 直接将集合作为数字变量进行处理。 &#34;绝对&#34;是关键字:

procedure Foo(FXSample: TFXSample);
var
  NumericFxSample: Byte absolute FXSample;
begin
WriteLn(YourTextFile, NumericFxSample);//numeric value from a set
end;

如果您的类型超过8位,则需要使用更宽的数字类型,例如单词(一组中最多16项)或双字。