Как сохранить/загрузить набор типов?
У меня есть этот код
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?
я хотел бы сохранить его в базе данных.
возможно ли это?
Ответы
Ответ 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)
. Таким образом, для 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
Ответ 2
Набор Delphi представляет собой просто набор (возможно) связанных булевых флагов. Каждый логический флаг соответствует тому, соответствует ли порядковое значение совпадения в наборе.
Конечно, вы можете упаковать набор в целочисленное значение, представив набор в качестве битового набора. Или вы можете создать текстовое представление набора.
Тем не менее, оба этих варианта оставляют вам возможность без проблем обрабатывать базу данных на уровне SQL. По этой причине я бы посоветовал вам представлять каждое значение в наборе, то есть каждый логический флаг, в виде отдельного поля (то есть столбца) таблицы базы данных. Это дает вам наиболее мощное представление данных.
Ответ 3
Непосредственно typecasting заданной переменной невозможно в 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;
Ответ 4
Самый простой способ сохранить набор в базе данных (как @DavidHeffernan, упомянутый в комментарии), - это преобразовать ваш набор в бит-маску.
в int32 (integer) значение имеет 32 бита и может сохранить set
до 32 полей;
Delphi имеет TIntegerSet
(см. http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.TIntegerSet) тип, определенный в SysUtils
. он объявляется как:
TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;
поэтому, используя его, просто преобразовать набор в целое и обратно (просто приведение 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.
Ответ 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 forms, добавьте к нему два компонента TButton и заполните события OnClick для каждой кнопки, как показано в примере ниже.
Это было создано в XE4, поэтому предложение uses может отличаться для других версий Delphi, но это должно быть тривиально изменить, удалив индикаторы пространства имен перед каждым блоком в разделе uses. Сохранение переменной типа набора с артикулированными значениями возможно для двоичного файла и легко с Delphi. Другими словами,
Также предлагается взглянуть на модуль TypInfo, если у вас есть источник или просто с использованием предоставленных функций, которые делают рассечение типов набора до их текстового представления довольно простым, хотя здесь не приводится никакого примера. Это рекомендуется, если вы хотите включить сохранение в конфигурационный файл или 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.
Ответ 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. если вам нужно больше настроек, вы можете добавить их, посмотрев код ниже.
Уставка может занимать всего 1 байт.
Таким образом, вы можете получить размер yourSet и получить результат как модуль этого результата.
пример: ваш размер набора: 1 байт, вы можете получить результат →
Результат: = pINT ^ mod maxVal
Вы должны получить maxval, вычисляя maxvalue типа переменной.
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.