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

  • 变体 1 非常简单,但需要一个“适配器”类型,例如TArray1 每个大小的 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 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的通用解决方案。一个声明适用于所有情况,因此不易出错。

于 2015-07-22T18:30:04.433 回答
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 回答
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 回答