9

(私はすでにCodeReviewでこれを尋ねましたが、トピック外として閉じられました。うまくいけば、ここでトピックになります。)

派生型の静的配列 (LabelsA: array[0..3] of TLabel;次のサンプル コードのように) と、基本型のオープン配列を受け入れるルーチン ( など) があり、それらの静的配列procedure DoSomethingWithControls(const AControls: array of TControl);を呼び出したいと考えています。DoSomethingWithControls私のサンプルを見てください:

procedure DoSomethingWithControls(const AControls: array of TControl);
var
  i: Integer;
begin
  for i := Low(AControls) to High(AControls) do
    Writeln(AControls[i].Name);
end;

procedure Test;
var
  LabelsA: array[0..3] of TLabel;
  LabelsB: array[0..1] of TLabel;

  procedure Variant1;
  type
    TArray1 = array[Low(LabelsA)..High(LabelsA)] of TControl;
    TArray2 = array[Low(LabelsB)..High(LabelsB)] of TControl;
  begin
    DoSomethingWithControls(TArray1(LabelsA));
    DoSomethingWithControls(TArray2(LabelsB));
  end;

  procedure Variant2;
  type
    TControlArray = array[0..Pred(MaxInt div SizeOf(TControl))] of TControl;
    PControlArray = ^TControlArray;
  begin
    DoSomethingWithControls(Slice(PControlArray(@LabelsA)^, Length(LabelsA)));
    DoSomethingWithControls(Slice(PControlArray(@LabelsB)^, Length(LabelsB)));
  end;

  procedure Variant3;
  var
    ControlsA: array[Low(LabelsA)..High(LabelsA)] of TControl absolute LabelsA;
    ControlsB: array[Low(LabelsB)..High(LabelsB)] of TControl absolute LabelsB;
  begin
    DoSomethingWithControls(ControlsA);
    DoSomethingWithControls(ControlsB);
  end;

begin
  Variant1;
  Variant2;
  Variant3;
end;

の呼び出しにはいくつかのバリエーションがありますDoSomethingWithControls

  • TArray1 バリアント 1 は非常に単純ですが、TLabel 配列のすべてのサイズのように「アダプター」型が必要です。もっと柔軟にしてほしい。

  • バリアント 2 はより柔軟で統一されていますが、見苦しく、エラーが発生しやすくなっています。

  • バリアント 3 ( TOndrej 提供) はバリアント 1 に似ています - 明示的なキャストは必要ありませんが、バリアント 1 は、何かを台無しにした場合 (たとえば、コピーと貼り付け中に配列の境界が間違っている場合) に、コンパイラのセキュリティを少し強化します。

これらの欠点なしで (配列の要素型を変更せずに) これらの呼び出しを定式化する方法はありますか? D2007 および XE6 で動作するはずです。

4

5 に答える 5

3

これらのキャストはすべてかなり醜いです。それらはすべて機能しますが、それらを使用すると汚く感じます。ヘルパー関数を使用することは完全に合理的です。

type
  TControlArray = array of TControl;

function ControlArrayFromLabelArray(const Items: array of TLabel): TControlArray;
var 
  i: Integer;
begin
  SetLength(Result, Length(Items));
  for i := 0 to high(Items) do
    Result[i] := Items[i];
end;

そして、次のように関数を呼び出します。

DoSomethingWithControls(ControlArrayFromLabelArray(...));

もちろん、ジェネリックを使用できれば、これは非常にクリーンになります。

于 2015-07-22T10:48:22.953 に答える
1

あまり美しくもありませんが、次のようにコンパイラをだますことができます。

procedure Variant3;
var
  ControlsA: array[Low(LabelsA)..High(LabelsA)] of TControl absolute LabelsA;
begin
  DoSomethingWithControls(ControlsA);
end;
于 2015-07-22T08:22:09.460 に答える
1

以下の例は、オープン配列パラメーターが内部でどのように実装されているかに基づいています。ただし、「入力された @ 演算子」では機能しません。

  procedure Variant4;
  type
    TCallProc = procedure (AControls: Pointer; HighBound: Integer);
  var
    CallProc: TCallProc;
  begin
    CallProc := @DoSomethingWithControls;

    CallProc(@LabelsA, Length(LabelsA) - 1);
    CallProc(@LabelsB, Length(LabelsB) - 1);
  end;

High(Labels)すべての静的配列が 0 ベースである限り、HighBound を渡す方がよいでしょう。

于 2015-07-22T18:49:57.840 に答える
1

オーバーロードされたプロシージャを宣言します。

procedure DoSomethingWithControls(const AControls: array of TControl); overload;
var
  i: Integer;
begin
  for i := 0 to High(AControls) do
    if Assigned(AControls[i]) then
       Writeln(AControls[i].Name)
    else
      WriteLn('Control item: ',i);
end;

procedure DoSomethingWithControls(const ALabels: array of TLabel); overload;
type
  TControlArray = array[0..Pred(MaxInt div SizeOf(TControl))] of TControl;
  PControlArray = ^TControlArray;
begin
  DoSomethingWithControls(Slice(PControlArray(@ALabels)^, Length(ALabels)));
end;

これは、バリアント 2 の一般的な解決策です。すべてのケースに対して 1 つの宣言を行うため、エラーが発生しにくくなります。

于 2015-07-22T18:30:04.433 に答える
0

動的配列はオープン配列としてメソッドに渡すことができるため、オプションは静的配列を動的配列に変換することです。

配列をコピーするオーバーヘッドが気にならない場合は、次の点を考慮してください。

ラベルのオープン配列を動的な TControlArray 配列に変換する関数を作成します。

type
  TControlArray = array of TControl;

{$IFOPT R+} {$DEFINE R_ON} {$R-} {$ENDIF}
function MakeControlArray(const ALabels: array of TLabel): TControlArray;
begin
  SetLength(Result, Length(ALabels));
  Move(ALabels[0], Result[0], Length(ALabels) * SizeOf(TObject));
end;
{$IFDEF R_ON} {$R+} {$UNDEF R_ON} {$ENDIF}

Variant4 は次のように記述できます。

procedure Variant4;
begin
  DoSomethingWithControls(MakeControlArray(LabelsA));
  DoSomethingWithControls(MakeControlArray(LabelsB));
end;

テストケース:

procedure TAdHocTests.TestLabelsToControls;
const
  LLabelsA: array[0..3] of TLabel = (Pointer(0),Pointer(1),Pointer(2),Pointer(3));
var
  LLoopI: Integer;
  LLabelsB: array[0..9] of TLabel;
  LEmptyArray: TLabelArray;
begin
  for LLoopI := Low(LLabelsB) to High(LLabelsB) do
  begin
    LLabelsB[LLoopI] := Pointer(LLoopI);
  end;

  DoSomethingWithControls(MakeControlArray(LLabelsA), Length(LLabelsA));
  DoSomethingWithControls(MakeControlArray(LLabelsB), Length(LLabelsB));
  DoSomethingWithControls(MakeControlArray([]), 0);
  DoSomethingWithControls(MakeControlArray(LEmptyArray), 0);
end;

procedure TAdHocTests.DoSomethingWithControls(
    const AControls: array of TControl;
    AExpectedLength: Integer);
var
  LLoopI: Integer;
begin
  CheckEquals(AExpectedLength, Length(AControls), 'Length incorrect');
  for LLoopI := Low(AControls) to High(AControls) do
  begin
    CheckEquals(LLoopI, Integer(AControls[LLoopI]));
  end;
end;
于 2015-07-22T11:08:52.360 に答える