【问题标题】:How to save/load Set of Types?如何保存/加载类型集?
【发布时间】:2012-03-04 08:40:10
【问题描述】:

我有这个代码

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 的属性?
我想将它保存在数据库中。
有可能吗?

【问题讨论】:

  • 你可以有一堆布尔字段。或者您可以使用每个标志位压缩成一个整数或多个整数。我会使用前者。
  • 查看Classes.pas 中的TWriter.WriteProperty 中的TReader.ReadSetWriteSet。这就是 VCL 流如何在 dfm 文件之间设置属性的方式。如果你使用这些,你需要在你的数据库中有一个字符串字段来存储写出的集合。请注意,采用这种方式比将值存储在单独的布尔字段中更容易读取/写入,但可能会使使用 SQL 过滤数据集变得更加困难。
  • 并不是说它是对是错,但是你为什么不创建一个名为TXSamples = set of TXSample的类型和然后字段FXSample: TXSamples...我很困惑为什么你在私人领域以这种方式声明它。你最好制作另一种类型TXSamples,然后将该类型用于FXSample...至少我一直看到它是这样做的,并且我自己也是这样做的。
  • @Jerry Dodge,如果不是很清楚,抱歉。我编辑了问题的详细信息。
  • @XBasic3000 并不是说​​有什么不清楚的,只是我强烈建议为这个集合定义一个类型,而不是每次需要使用它时都强制转换它。

标签: delphi types typecast-operator


【解决方案1】:

如果您的集合永远不会超过 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)。因此,在将TXSamples 类型转换为时,以下范围适用于Ord(High(TXSample))

  • Byte: Ord(High(TXSample)) < 8
  • Word: 8 <= Ord(High(TXSample)) < 16
  • Longword: 16 <= Ord(High(TXSample)) < 32
  • UInt64: 32 <= Ord(High(TXSample)) < 64

【讨论】:

  • 这真的有效吗?将集合转换为整数时出现 E2089 Invalid typecast 错误。
  • @Dangph 是的。您是否检查了最大设置大小/枚举边界? (例如,当您的集合由具有 16 种可能性的枚举组成时,您必须将集合转换为 Word。当最大集合大小为 17 时,您必须转换为 Integer。)
  • 我明白了,谢谢。我需要转换为字节,而不是整数。我假设编译器会自动将值扩大为整数。
  • 非常感谢,这真的很酷,我使用以下代码将一个 8 行压缩为一个:CustomFont.Style := TFontStyles(Byte( (ord(fsBold) and (Ord(isBold) shl Byte(ord(fsBold)))) or (ord(fsItalic) and (Ord(isItalic) shl Byte(ord(fsItalic)))) or (ord(fsStrikeOut) and (Ord(isStrikeout) shl Byte(ord(fsStrikeOut)))) or (ord(fsUnderline) and (Ord(isUnderline) shl Byte(ord(fsUnderline))))) ); 其中 isBold 等是布尔值
  • 在 32 位 Delphi 中,对于 32 到 64 之间的大小,您不能转换为 UInt64。请参阅我的答案,了解 32 位和 64 位之间的区别。 stackoverflow.com/questions/30336620/…
【解决方案2】:

在 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;

【讨论】:

  • 最好在某个地方测试sizeof(TMySet) = sizeof(integer),以防万一您的集合太小或太大。
【解决方案3】:

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

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

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

【讨论】:

  • 视情况而定。如果您在集合的枚举中有超过 20 个元素,那么为每个元素创建列可能只是浪费资源。我个人会为最多 5 个元素制作列。对于更多的人,我会在 SQL 查询中使用按位运算符(虽然不太舒服),或者对于那些知道 SQL 但不能使用按位运算符的人,我会创建表视图。
【解决方案4】:

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

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

因此使用它,将 set 转换为整数并返回很简单(只需将 TIgeterSet 转换为整数,反之亦然);

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

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

这里是一个使用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.

【讨论】:

    【解决方案5】:

    就个人而言,我会将集合转换为整数并将其作为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.
    

    【讨论】:

      【解决方案6】:

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

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

      这是在 XE4 中创建的,因此对于其他版本的 Delphi,uses 子句可能会有所不同,但通过删除uses 子句中每个单元之前的命名空间指示符应该很容易改变。使用 Delphi 可以轻松地将具有明确值的集合类型变量保存到二进制文件中。换句话说,

      如果您有源代码或仅使用提供的函数,还建议您查看 TypInfo 单元,这使得将 Set 类型分解为它们的文本表示相当简单,尽管此处没有提供示例。如果您想包括保存到配置或 ini 文件或以文本可编辑的持久性格式,则建议这样做。

      下面是我所知道的最简单的一个。查看保存到如下流的集合类型的二进制输出意味着它基于集合的大小以最小的可能位图表示形式保存。下面的一个映射到磁盘上的一个字节(值为 5),这意味着每个值必须映射到 2 的幂(seThis = 1,seThat = 2,seTheOther = 4),就像手动创建的常量位掩码值一样。编译器可能会强制它遵循强制 set 保持其序数的规则。这个例子是在 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.
      

      【讨论】:

        【解决方案7】:

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

        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=+-* ..) .

        Delphi 中的这一行:

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

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

        lea eax,[ebp-$0c]
        mov ax,[eax]
        mov [ebp-$06],ax
        

        在 Delphi 2007 上(TShiftState 较小,因此可以使用 Byte)这个汇编程序:

        movzx eax,[esi]
        mov [ebp-$01],al
        

        【讨论】:

          【解决方案8】:

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

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

          例子:你设置的大小:1字节你可以得到结果-->

          结果 := pINT^ mod 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.
          

          【讨论】:

            【解决方案9】:

            最简单的解决方案 - 直接将集合作为数字变量进行。 “绝对”是一个关键字:

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

            如果您的类型超过 8 位,则需要使用更宽的数字类型,例如 word(一组最多 16 个项目)或 dword。

            【讨论】:

              【解决方案10】:

              在 RTTI 的帮助下,它可以通过通用方式实现:

              program SetConverter;
              
              {$APPTYPE CONSOLE}
              {$R *.res}
              
              uses
                System.RTTI, System.SysUtils;
              
              type
                SetConverter<T> = class abstract
                strict private
                  class var FRttiContext: TRttiContext;
                public
                  class function ToInteger(aSet: T): Integer;
                  class function FromInteger(aValue: Integer): T;
                end;
              
                { SetConverter<T> }
              
              class function SetConverter<T>.FromInteger(aValue: Integer): T;
              var
                ResultValues: TIntegerSet;
                ReturnType: TRttiType;
                SetValues: TIntegerSet;
                EnumSet: T absolute SetValues;
              begin
                ReturnType := FRttiContext.GetType(Self).GetMethod('FromInteger').ReturnType;
              
                if not((ReturnType is TRttiSetType) and (TRttiSetType(ReturnType).ElementType is TRttiEnumerationType)) then
                  Exit;
              
                SetValues := TIntegerSet(aValue);
                Result := EnumSet;
              end;
              
              class function SetConverter<T>.ToInteger(aSet: T): Integer;
              var
                RttiParameter: TRttiParameter;
                ResultValues: TIntegerSet;
                SetValues: TIntegerSet;
                EnumSet: T absolute SetValues;
                EnumType: TRttiEnumerationType;
                SetType: TRttiSetType;
                i: Integer;
              begin
                Result := 0;
                RttiParameter := FRttiContext.GetType(Self).GetMethod('ToInteger').GetParameters[0];
                if not(RttiParameter.ParamType is TRttiSetType) then
                  Exit;
              
                SetType := RttiParameter.ParamType as TRttiSetType;
              
                if not(SetType.ElementType is TRttiEnumerationType) then
                  Exit;
              
                EnumType := SetType.ElementType as TRttiEnumerationType;
              
                EnumSet := aSet;
              
                ResultValues := [];
              
                for i := EnumType.MinValue to EnumType.MaxValue do
                  if i in SetValues then
                    Include(ResultValues, i);
              
                Result := Integer(ResultValues);
              end;
              
              type
                TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType7, xsType8);
                TSampleSet = set of TXSample;
              
              var
                Before, After: TSampleSet;
                i: Integer;
              begin
                Before := [xsType2, xsType6 .. xsType8];
                i := SetConverter<TSampleSet>.ToInteger(Before);
                After := SetConverter<TSampleSet>.FromInteger(i);
              
                WriteLN('Before = After: ' + (Before = After).ToString(TUseBoolStrs.True));
                Readln;
              end.
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-05-31
                • 1970-01-01
                • 1970-01-01
                • 2011-11-30
                相关资源
                最近更新 更多