8

レコードから実際には何も派生できないことはわかっていますが、問題を 1 つの文に要約する方法がわかりません。その場合はタイトルを編集してください。

ここでやりたいことは、ジェネリック型の配列を作成することです。これは、X 個の型の 1 つにすることができます。配列は、それらのカスタム型で満たされます (それらには異なるフィールドがあり、それが重要です)。簡単な方法は、バリアント レコードの配列を作成することです。各バリアントには独自の型がありますが、次のように識別子を再宣言することはできません。

GenericRec = Record
  case SubTypeName: TSubTypeName of
    type1name: (SubRec: Type1);
    type2name: (SubRec: Type2);
    ...
    typeNname: (SubRec: TypeN);
  end;

に変更SubRecするSubRec1, SubRec2... SubRecNと参照が難しくなりますが、不可能ではありません。そして、上記の問題に対する代替ソリューションを探し始めたので、クラスが頭に浮かびました。

私が達成しようとしていることを示す明白な例はTObject、それらの配列をさまざまなものに割り当てることができることです。それが私が望んでいることですが、レコードを使用して(そしてそれを行うことは不可能です)、レコードをファイルに保存したり、それらを読み戻したりできるようにしたいからです(また、それは私がすでに慣れ親しんでいるためです)。自分の単純なクラスを作成することは問題ではありません。それから子孫クラスを作成して、サブタイプを表します。それは可能です。しかし、それをファイルに書き込んで読み返すのはどうでしょうか? これはシリアル化に要約されますが、これを行う方法がわかりません。私が集めたものから、それはそれほど簡単ではなく、クラスは から派生している必要がありますTComponent

TMyClass = Class

上記のようにクラスを作っても違いはありますか?特別なものではなく、いくつかのカスタム タイプを含めて最大 10 個のフィールドがあります。

シリアライゼーションはさておき (そのトピックについては、読むべきことがたくさんあるという理由だけで)、ここでクラスを使用することも問題外かもしれません。

この時点で、私のオプションは何ですか? レコードを放棄して、クラスでこれを試す必要がありますか? それとも、レコードに固執し、バリアントの「制限」に対処するだけで、それほど複雑ではないでしょうか? 私は学ぶことに全力を注いでおり、クラスのアプローチを爆発させることで私がより賢くなる可能性がある場合は、そうします。私も調べてみましたTListが(使用したことはありません)、レコードとの相性があまり良くないようです。私はあらゆる種類の提案を受け入れます。私は何をしますか?

4

3 に答える 3

6

シリアライゼーションを「1回のBlockWrite呼び出しですべてをディスクに書き込む」と混同しています。TComponentまたはの子孫であるかどうかに関係なく、必要なものをシリアル化できますTPersistent

1 回の呼び出しですべてを記述することBlockWriteは最初は便利に見えますが、目的のレコード タイプが特に興味深いもの (文字列、動的配列、インターフェイス、オブジェクト、またはその他の参照など) を格納する場合は、実際には望んでいないことがすぐにわかります。またはポインターベースの型)。

最小公分母に合わせてコーディングすることになるため、おそらくバリアント レコードも満足のいくものではないことに気付くでしょう。実際に含まれている型を確認せずにレコード内の何かにアクセスすることはできず、最小量のデータのサイズでも、最大のデータ型と同じ量のスペースを占有します。

質問はポリモーフィズムを説明しているように見えるので、言語がすでに提供しているものを受け入れることもできます。オブジェクトの配列 (またはリスト、またはその他のコンテナー) を使用します。次に、仮想メソッドを使用して、それらをすべて均一に処理できます。必要に応じて、レコードの動的ディスパッチを実装できます (たとえば、各レコードに、そのレコードに含まれるデータ型を処理する方法を知っている関数を参照する関数ポインターを与える)。 .

于 2012-06-28T23:13:26.553 に答える
5

このようなデータを処理する「自然な」方法は、classではなくを使用することrecordです。定義時と実装を扱うときの両方で、作業がはるかに簡単になります。特に、virtualメソッドは、特定の種類のクラスのプロセスをカスタマイズするのに非常に強力です。次に、Delphi の新しいバージョンではTList/TObjectListTCollection、または汎用ベースの配列を使用して、リストを格納します。

シリアル化については、いくつかの方法があります。Delphi: データをある種の構造体に格納するを参照してください。

あなたの特定のケースでは、使用している「バリアント」の種類のレコードに問題があります。私見の主な欠点は、コンパイラが「バリアント」部分内で参照カウントされた種類の変数(たとえば a )を設定することを拒否することstringです。integerしたがって、この「バリアント」部分内には「プレーンな」変数 ( など) のみを書き込むことができます。このソリューションの関心を低下させる大きな制限IMHO。

もう 1 つの可能性は、レコードの種類をその定義の先頭に格納することです。たとえば、 a を使用するか、数値よりも明示的なRecType: integeraを使用することをお勧めします。RecType: TEnumerationTypeただし、多くのコードを手動で記述する必要があり、ポインターを操作する必要があります。これは、ポインターのコーディングにあまり精通していない場合、エラーが発生しやすくなります。

そのため、 からアクセスできるレコードのタイプ情報を保存することもできますTypeInfo(aRecordVariable)FillChar次に、割り当ての直後にレコードの内容をゼロに初期化するために使用できます。次に、解放の直後に次の関数を使用してレコードの内容を確定します (これはDispose()内部的に行われるものであり、呼び出す必要があります。そうしないと、メモリ リークが発生します)。 :

procedure RecordClear(var Dest; TypeInfo: pointer);
asm
  jmp System.@FinalizeRecord
end;

しかし、そのような実装パターンは車輪を再発明するだけです! 実際には がどのようclassに実装されているかです: 任意のTObjectインスタンスの最初の要素は、その へのポインタClassTypeです:

function TObject.ClassType: TClass;
begin
  Pointer(Result) := PPointer(Self)^;
end;

Delphi には、 と呼ばれる別の構造もありobjectます。これは一種ですがrecord、継承をサポートしています。この記事を参照してください。これは、Turbo Pascal 5.5 日で古いスタイルの OOP プログラミングであり、非推奨ですが、まだ利用できます。Delphi の新しいバージョンで奇妙なコンパイルの問題を発見したことに注意してください。objectスタックに割り当てられた が常に初期化されないことがあります。

任意のコンテンツをバイナリまたは JSON にTDynArrayシリアル化できるラッパーとそれに関連する関数を見てください。Delphi(win32)シリアル化ライブラリの質問recordを参照してください。非バリアント部分にa が含まれていても、バリアント レコードでは機能しますが、プレーンな「Write/BlockWrite」は参照カウント フィールドでは機能しません。string

于 2012-06-29T07:44:34.787 に答える
2

レコードでこれを行うには、前に共通のフィールドを持つさまざまなレコード タイプを作成し、それらの同じフィールドをジェネリック レコードに配置します。次に、必要に応じて、汎用レコードへのポインターを特定のレコードへのポインターに型キャストするだけです。例えば:

type
  PGenericRec = ^GenericRec;
  GenericRec = Record 
    RecType: Integer;
  end;

  PType1Rec = ^Type1Rec; 
  Type1Rec = Record 
    RecType: Integer;
    // Type1Rec specific fields...
  end;

  PType2Rec = ^Type2Rec; 
  Type2Rec = Record 
    RecType: Integer;
    // Type2Rec specific fields...
  end;

  PTypeNRec = ^TypeNRec;
  TypeNRec = Record
    RecType: Integer;
    // TypeNRec specific fields...
  end; 

var
  Recs: array of PGenericRec;
  Rec1: PType1Rec; 
  Rec2: PType2Rec; 
  RecN: PTypeNRec;
  I: Integer;
begin
  SetLength(Recs, 3);

  New(Rec1);
  Rec1^.RecType := RecTypeForType1Rec;
  // fill Rec1 fields ...
  Recs[0] := PGenericRec(Rec1);

  New(Rec2);
  Rec2^.RecType := RecTypeForType2Rec;
  // fill Rec2 fields ...
  Recs[1] := PGenericRec(Rec2);

  New(RecN);
  Rec3^.RecType := RecTypeForTypeNRec;
  // fill RecN fields ...
  Recs[2] := PGenericRec(RecN);

  for I := 0 to 2 do
  begin
    case Recs[I]^.RecType of
      RecTypeForType1Rec: begin
        Rec1 := PType1Rec(Recs[I]);
        // use Rec1 as needed...
      end;
      RecTypeForType1Re2: begin
        Rec2 := PType2Rec(Recs[I]);
        // use Rec2 as needed...
      end;
      RecTypeForTypeNRec: begin
        RecN := PTypeNRec(Recs[I]);
        // use RecN as needed...
      end;
    end;
  end;

  for I := 0 to 2 do
  begin
    case Recs[I]^.RecType of
      RecTypeForType1Rec: Dispose(PType1Rec(Recs[I]));
      RecTypeForType2Rec: Dispose(PType2Rec(Recs[I]));
      RecTypeForTypeNRec: Dispose(PTypeNRec(Recs[I]));
    end;
  end;
end;

シリアル化に関しては、その必要はありませんTComponent。手動で行うだけで、レコードをシリアル化できます。書き込む場合は、RecType最初に値を書き出し、次にレコード固有の値を書き出します。読み取りの場合は、RecType最初に値を読み取り、次にその値に適したレコード タイプを作成してから、レコード固有の値を読み取ります。

interface

type
  PGenericRec = ^GenericRec;
  GenericRec = Record 
    RecType: Integer;
  end;

  NewRecProc = procedure(var Rec: PGenericRec);
  DisposeRecProc = procedure(Rec: PGenericRec);
  ReadRecProc = procedure(Rec: PGenericRec);
  WriteRecProc = procedure(const Rec: PGenericRec);

function NewRec(ARecType: Integer): PGenericRec;
procedure DisposeRec(var Rec: PGenericRec);
procedure ReadRec(Rec: PGenericRec);
procedure WriteRec(const Rec: PGenericRec);

procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc);

implementation

type
  TRecTypeReg = record
    RecType: Integer;
    NewProc: NewRecProc;
    DisposeProc: DisposeRecProc;
    ReadProc: ReadRecProc;
    WriteProc: WriteRecProc;
  end;

var
  RecTypes: array of TRecTypeReg;

function NewRec(ARecType: Integer): PGenericRec;
var
  I: Integer;
begin
  Result := nil;
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = ARecType then
      begin
        NewProc(Result);
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure DisposeRec(var Rec: PGenericRec);
var
  I: Integer;
begin
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = Rec^.RecType then
      begin
        DisposeProc(Rec);
        Rec := nil;
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure ReadRec(var Rec: PGenericRec);
var
  LRecType: Integer;
  I: Integer;
begin
  Rec := nil;
  LRecType := ReadInteger;
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = LRecType then
      begin
        NewProc(Rec);
        try
          ReadProc(Rec);
        except
          DisposeProc(Rec);
          raise;
        end;
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure WriteRec(const Rec: PGenericRec);
var
  I: Integer;
begin
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = Rec^.RecType then
      begin
        WriteInteger(Rec^.RecType);
        WriteProc(Rec);
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc);
begin
  SetLength(RecTypes, Length(RecTypes)+1);
  with RecTypes[High(RecTypes)] do
  begin
    RecType := ARecType;
    NewProc := ANewProc;
    DisposeProc := ADisposeProc;
    ReadProc := AReadProc;
    WriteProc := AWriteProc;
  end;
end;

end.

.

type
  PType1Rec = ^Type1Rec; 
  Type1Rec = Record 
    RecType: Integer;
    Value: Integer;
  end;

procedure NewRec1(var Rec: PGenericRec);
var
  Rec1: PType1Rec;
begin
  New(Rec1);
  Rec1^.RecType := RecTypeForType1Rec;
  Rec := PGenericRec(Rec1);
end;

procedure DisposeRec1(Rec: PGenericRec);
begin
  Dispose(PType1Rec(Rec));
end;

procedure ReadRec1(Rec: PGenericRec);
begin
  PType1Rec(Rec)^.Value := ReadInteger;
end;

procedure WriteRec1(const Rec: PGenericRec);
begin
  WriteInteger(PType1Rec(Rec)^.Value);
end;

initialization
  RegisterRecType(RecTypeForType1Rec, @NewRec1, @DisposeRec1, @ReadRec1, @WriteRec1);

.

type
  PType2Rec = ^Type2Rec; 
  Type2Rec = Record 
    RecType: Integer;
    Value: Boolean;
  end;

procedure NewRec2(var Rec: PGenericRec);
var
  Rec2: PType2Rec;
begin
  New(Rec2);
  Rec2^.RecType := RecTypeForType2Rec;
  Rec := PGenericRec(Rec2);
end;

procedure DisposeRec2(Rec: PGenericRec);
begin
  Dispose(PType2Rec(Rec));
end;

procedure ReadRec2(Rec: PGenericRec);
begin
  PType2Rec(Rec)^.Value := ReadBoolean;
end;

procedure WriteRec2(const Rec: PGenericRec);
begin
  WriteBoolean(PType2Rec(Rec)^.Value);
end;

initialization
  RegisterRecType(RecTypeForType2Rec, @NewRec2, @DisposeRec2, @ReadRec2, @WriteRec2);

.

type
  PTypeNRec = ^Type2Rec; 
  TypeNRec = Record 
    RecType: Integer;
    Value: String;
  end;

procedure NewRecN(var Rec: PGenericRec);
var
  RecN: PTypeNRec;
begin
  New(RecN);
  RecN^.RecType := RecTypeForTypeNRec;
  Rec := PGenericRec(RecN);
end;

procedure DisposeRecN(Rec: PGenericRec);
begin
  Dispose(PTypeNRec(Rec));
end;

procedure ReadRecN(Rec: PGenericRec);
begin
  PTypeNRec(Rec)^.Value := ReadString;
end;

procedure WriteRecN(const Rec: PGenericRec);
begin
  WriteString(PTypeNRec(Rec)^.Value);
end;

initialization
  RegisterRecType(RecTypeForTypeNRec, @NewRecN, @DisposeRecN, @ReadRecN, @WriteRecN);

.

var
  Recs: array of PGenericRec;

procedure CreateRecs;
begin
  SetLength(Recs, 3);

  NewRec1(Recs[0]);
  PRecType1(Recs[0])^.Value : ...;

  NewRec2(Recs[1]);
  PRecType2(Recs[1])^.Value : ...;

  NewRecN(Recs[2]);
  PRecTypeN(Recs[2])^.Value : ...;
end;

procedure DisposeRecs;
begin
  for I := 0 to High(Recs) do
    DisposeRec(Recs[I]);
  SetLength(Recs, 0);
end;

procedure SaveRecs;
var
  I: Integer;
begin
  WriteInteger(Length(Recs));
  for I := 0 to High(Recs) do
    WriteRec(Recs[I]);
end;

procedure LoadRecs;
var
  I: Integer;
begin
  DisposeRecs;
  SetLength(Recs, ReadInteger);
  for I := 0 to High(Recs) do
    ReadRec(Recs[I]);
end;
于 2012-06-28T23:10:27.180 に答える