为什么我不能返回任意字符串数组?

7
编译器允许我执行以下操作:
procedure MyProc(const ADynData: array of string);

或者

procedure MyProc(const ADynData: TStringDynArray);

可以像这样传递任意数据:

MyProc(['Data1', 'Data2']);

然而,不允许。
function MyFunc: TStringDynArray;
....
function MyFunc: TStringDynArray;
begin
    Result := ['Data1', 'Data2'];
end;

或者

function MyFunc: TStringDynArray;
const
    CDynData: array[0..1] of string = ('Data1', 'Data2');
begin
    Result := CDynData;
end;

为什么会这样?这不是从技术上讲同一件事吗?

对于这些特定的情况,返回任意字符串数组的推荐(和最有效)方法是什么?


在Delphi中,使用var-array-of参数而不是尝试返回动态数组是惯用的。请记住,Delphi没有C++那样大而复杂的值和引用语义集合,这对你从Delphi函数中返回什么施加了一些限制。特别是,Delphi对动态数组的内存管理更适合使用var数组(进行返回)。如果您使用“var”而不是const,则上面的示例将更直接可比,因为返回数组和传递数组在内存管理方面是两个非常不同的事情。 - Warren P
1
@WarrenP 不用这么说。事实上,返回一个动态数组是惯用法。这样调用者可以决定数组的长度。当前的习惯是返回 TArray<T>。试着设置一个开放数组的长度吧! - David Heffernan
好的,在支持泛型的现代Delphi版本中,David是正确的。然而,那些仍然不信任泛型代码的经典Delphi老顽固们仍然不会使用泛型。因此,我们使用var参数,并让经典技术继续像以往一样运作。下次我们看到在QualityCentral上发布了一个可怕的故障,当编译器遇到错误时整个程序都崩溃了...我们会尽量不去幸灾乐祸。 :-) - Warren P
1
@WarrenP 我再问一遍,当被调用者确定长度时,你会怎么做?即使在 Delphi 的泛型版本之前,答案也是相同的。 - David Heffernan
好观点。避免旧模式有其合理的理由。 - Warren P
显示剩余3条评论
3个回答

11

不,这不是同一件事。在

procedure MyProc(const ADynData: array of string);

该参数是一个开放数组参数,这与普通的动态数组不同。 [..] 语法只能用于在函数的开放数组参数中创建开放数组。 (否则,[..] 用于在代码中指定集合,例如Font.Style:= [fsBold,fsItalic]。但是集合只能有序类型作为它们的“基本类型”,因此仍然没有“字符串集合”之类的东西。)
换句话说,在代码中无法像您在第二个代码片段中尝试的那样编写动态数组。
function MyFunc: TStringDynArray;
begin
  result := ['Data1', 'Data2']; // Won't work.
end;

然而,在新版本的Delphi中,这几乎是可能的:
type
  TStringDynArray = array of string;

function MyFunc: TStringDynArray;
begin
  result := TStringDynArray.Create('A', 'B');
end;

最后,

function MyFunc: TStringDynArray;
const
  CDynData: array[0..1] of string = ('Data1', 'Data2');
begin
  result := CDynData;
end;

TStringDynArray 是一个动态数组,而 CDynData 是一个静态数组,这两种是不同的基础类型,所以该方法无法工作。


@James:没错,开放数组只能用于参数。 - Andreas Rejbrand
嗯,看起来有点受限制,除非他们不允许有技术原因。如果每次创建TStringDynArray,你认为会有任何性能影响吗?如果它不会改变,缓存结果是否值得? - James
@James:*这是否值得...*:常识和测试会回答这个问题。 - Andreas Rejbrand
是的,我明白了...我会这么做的。我想问的是,你是否认为创建TStringDynArray是一项昂贵的操作。公平地说,常识告诉我们“缓存一切”,但我们知道这并不总是必要的。 - James
你知道其他类似于TStringDynArray.Create的隐藏宝藏吗?例如TStringDynArray.AddTStringDynArray.Slice,我在哪里可以找到更多相关信息呢? - Marjan Venema
显示剩余2条评论

6

这个结构

['string1', 'string2'] 

被称为开放数组构造函数。来自文档:

开放数组构造函数允许您在函数和过程调用中直接构造数组。

它们只能作为开放数组参数或变体开放数组参数传递。

因此,您不能使用开放数组构造函数创建函数返回值。


如果您需要返回数组中固定数量的元素,则可以使用动态数组构造函数:
Result := TStringDynArray.Create('string1', 'string2');

然而,这对于可变数量的元素将无法起作用。现在,我知道您问题中的示例数组仅具有恒定数量的元素。但我相信您会遇到需要比动态数组构造函数提供更多灵活性的情况。
如果您希望创建现有动态数组的副本并返回该副本,请使用Copy
Result := Copy(SomeOtherDynamicArray);

当你手头有一个开放数组时,这种方法就会失效。你不能将一个开放数组传递给Copy。个人认为这是相当遗憾的,因为开放数组参数非常灵活和有用,我希望能尽可能地看到RTL对它们的支持。
因此,你最终不得不为这些情况编写辅助函数。你可以为每种数组类型编写专用的辅助函数,但这变得有些繁琐。这就是泛型发挥作用的地方。我有一个专门用于此目的的辅助类。以下是相关的摘录:
type
  TArray = class(Generics.Collections.TArray)
    ....
    class function Copy<T>(const Source: array of T): TArray<T>; overload; static;
    ....
  end;

class function TArray.Copy<T>(const Source: array of T): TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Length(Source));
  for i := 0 to high(Result) do begin
    Result[i] := Source[i];
  end;
end;

现在,这适用于您的字符串数组,也适用于任何其他类型。像这样调用它:
Result := TArray.Copy<string>(SomeStringOpenArray);

一个关键点是,我们使用的是动态数组的通用版本,TArray<string>,而不是TStringDynArray。如果你想认真使用泛型,那么这一点是必要的。这是因为TStringDynArray不能与TArray<string>或任何其他声明为array of string的类型兼容。将代码库更改为始终使用TArray<T>会带来回报。

以防有人对此辅助类的其余部分感兴趣,这里是它:

type
  TArray = class(Generics.Collections.TArray)
  private
    class function Comparison<T>(SortType: TSortType): TComparison<T>; static;
    class function Comparer<T>(const Comparison: TComparison<T>): IComparer<T>; static;
  public
    class procedure Swap<T>(var Left, Right: T); static;
    class procedure Reverse<T>(var Values: array of T); static;
    class function Reversed<T>(const Values: array of T): TArray<T>; static;
    class function Contains<T>(const Values: array of T; const Item: T; out ItemIndex: Integer): Boolean; overload; static;
    class function Contains<T>(const Values: array of T; const Item: T): Boolean; overload; static;
    class function IndexOf<T>(const Values: array of T; const Item: T): Integer; static;
    class function Sorted<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer): Boolean; overload; static;
    class function Sorted<T>(var Values: array of T; SortType: TSortType): Boolean; overload; static;
    class function Sorted<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer): Boolean; overload; static;
    class function Sorted<T>(var Values: array of T; const Comparison: TComparison<T>): Boolean; overload; static;
    class function Sorted<T>(GetValue: TFunc<Integer,T>; const Comparison: TComparison<T>; Index, Count: Integer): Boolean; overload; static;
    class procedure Sort<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer); overload; static;
    class procedure Sort<T>(var Values: array of T; SortType: TSortType); overload; static;
    class procedure Sort<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer); overload; static;
    class procedure Sort<T>(var Values: array of T; const Comparison: TComparison<T>); overload; static;
    class function Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>; overload; static;
    class function Copy<T>(const Source: array of T): TArray<T>; overload; static;
    class procedure Move<T>(const Source: array of T; var Dest: array of T; Index, Count: Integer); overload; static;
    class procedure Move<T>(const Source: array of T; var Dest: array of T); overload; static;
    class function Concatenated<T>(const Source1, Source2: array of T): TArray<T>; overload; static;
    class function Concatenated<T>(const Source: array of TArray<T>): TArray<T>; overload; static;
    class procedure Initialise<T>(var Values: array of T; const Value: T); static;
    class procedure Zeroise<T>(var Values: array of T); static;
    class function GetHashCode<T>(const Values: array of T): Integer; overload; static;
    class function GetHashCode<T>(Values: Pointer; Count: Integer): Integer; overload; static;
  end;

class function TArray.Comparison<T>(SortType: TSortType): TComparison<T>;
var
  DefaultComparer: IComparer<T>;
begin
  DefaultComparer := TComparer<T>.Default;
  Result :=
    function(const Left, Right: T): Integer
    begin
      case SortType of
      stIncreasing:
        Result := DefaultComparer.Compare(Left, Right);
      stDecreasing:
        Result := -DefaultComparer.Compare(Left, Right);
      else
        RaiseAssertionFailed(Result);
      end;
    end;
end;

class function TArray.Comparer<T>(const Comparison: TComparison<T>): IComparer<T>;
begin
  Result := TComparer<T>.Construct(Comparison);
end;

class procedure TArray.Swap<T>(var Left, Right: T);
var
  temp: T;
begin
  temp := Left;
  Left := Right;
  Right := temp;
end;

class procedure TArray.Reverse<T>(var Values: array of T);
var
  bottom, top: Integer;
begin
  bottom := 0;
  top := high(Values);
  while top>bottom do begin
    Swap<T>(Values[bottom], Values[top]);
    inc(bottom);
    dec(top);
  end;
end;

class function TArray.Reversed<T>(const Values: array of T): TArray<T>;
var
  i, j, Count: Integer;
begin
  Count := Length(Values);
  SetLength(Result, Count);
  j := Count-1;
  for i := 0 to Count-1 do begin
    Result[i] := Values[j];
    dec(j);
  end;
end;

class function TArray.Contains<T>(const Values: array of T; const Item: T; out ItemIndex: Integer): Boolean;
var
  DefaultComparer: IEqualityComparer<T>;
  Index: Integer;
begin
  DefaultComparer := TEqualityComparer<T>.Default;
  for Index := 0 to high(Values) do begin
    if DefaultComparer.Equals(Values[Index], Item) then begin
      ItemIndex := Index;
      Result := True;
      exit;
    end;
  end;
  ItemIndex := -1;
  Result := False;
end;

class function TArray.Contains<T>(const Values: array of T; const Item: T): Boolean;
var
  ItemIndex: Integer;
begin
  Result := Contains<T>(Values, Item, ItemIndex);
end;

class function TArray.IndexOf<T>(const Values: array of T; const Item: T): Integer;
begin
  Contains<T>(Values, Item, Result);
end;

class function TArray.Sorted<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer): Boolean;
begin
  Result := Sorted<T>(Values, Comparison<T>(SortType), Index, Count);
end;

class function TArray.Sorted<T>(var Values: array of T; SortType: TSortType): Boolean;
begin
  Result := Sorted<T>(Values, Comparison<T>(SortType));
end;

class function TArray.Sorted<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer): Boolean;
var
  i: Integer;
begin
  for i := Index+1 to Index+Count-1 do begin
    if Comparison(Values[i-1], Values[i])>0 then begin
      Result := False;
      exit;
    end;
  end;
  Result := True;
end;

class function TArray.Sorted<T>(var Values: array of T; const Comparison: TComparison<T>): Boolean;
begin
  Result := Sorted<T>(Values, Comparison, 0, Length(Values));
end;

class function TArray.Sorted<T>(GetValue: TFunc<Integer, T>; const Comparison: TComparison<T>; Index, Count: Integer): Boolean;
var
  i: Integer;
begin
  for i := Index+1 to Index+Count-1 do begin
    if Comparison(GetValue(i-1), GetValue(i))>0 then begin
      Result := False;
      exit;
    end;
  end;
  Result := True;
end;

class procedure TArray.Sort<T>(var Values: array of T; SortType: TSortType; Index, Count: Integer);
begin
  Sort<T>(Values, Comparison<T>(SortType), Index, Count);
end;

class procedure TArray.Sort<T>(var Values: array of T; SortType: TSortType);
begin
  Sort<T>(Values, SortType, 0, Length(Values));
end;

class procedure TArray.Sort<T>(var Values: array of T; const Comparison: TComparison<T>; Index, Count: Integer);
begin
  if not Sorted<T>(Values, Comparison, Index, Count) then begin
    Sort<T>(Values, Comparer<T>(Comparison), Index, Count);
  end;
end;

class procedure TArray.Sort<T>(var Values: array of T; const Comparison: TComparison<T>);
begin
  Sort<T>(Values, Comparison, 0, Length(Values));
end;

class function TArray.Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Count);
  for i := 0 to high(Result) do begin
    Result[i] := Source[i+Index];
  end;
end;

class function TArray.Copy<T>(const Source: array of T): TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Length(Source));
  for i := 0 to high(Result) do begin
    Result[i] := Source[i];
  end;
end;

class procedure TArray.Move<T>(const Source: array of T; var Dest: array of T; Index, Count: Integer);
var
  i: Integer;
begin
  for i := 0 to Count-1 do begin
    Dest[i] := Source[i+Index];
  end;
end;

class procedure TArray.Move<T>(const Source: array of T; var Dest: array of T);
var
  i: Integer;
begin
  for i := 0 to high(Source) do begin
    Dest[i] := Source[i];
  end;
end;

class function TArray.Concatenated<T>(const Source1, Source2: array of T): TArray<T>;
var
  i, Index: Integer;
begin
  SetLength(Result, Length(Source1)+Length(Source2));
  Index := 0;
  for i := low(Source1) to high(Source1) do begin
    Result[Index] := Source1[i];
    inc(Index);
  end;
  for i := low(Source2) to high(Source2) do begin
    Result[Index] := Source2[i];
    inc(Index);
  end;
end;

class function TArray.Concatenated<T>(const Source: array of TArray<T>): TArray<T>;
var
  i, j, Index, Count: Integer;
begin
  Count := 0;
  for i := 0 to high(Source) do begin
    inc(Count, Length(Source[i]));
  end;
  SetLength(Result, Count);
  Index := 0;
  for i := 0 to high(Source) do begin
    for j := 0 to high(Source[i]) do begin
      Result[Index] := Source[i][j];
      inc(Index);
    end;
  end;
end;

class procedure TArray.Initialise<T>(var Values: array of T; const Value: T);
var
  i: Integer;
begin
  for i := 0 to high(Values) do begin
    Values[i] := Value;
  end;
end;

class procedure TArray.Zeroise<T>(var Values: array of T);
begin
  Initialise<T>(Values, Default(T));
end;

{$IFOPT Q+}
  {$DEFINE OverflowChecksEnabled}
  {$Q-}
{$ENDIF}
class function TArray.GetHashCode<T>(const Values: array of T): Integer;
// see https://dev59.com/enI-5IYBdhLWcg3w18V3 and https://dev59.com/_2XWa4cB1Zd3GeqPO6Nr
var
  Value: T;
  EqualityComparer: IEqualityComparer<T>;
begin
  EqualityComparer := TEqualityComparer<T>.Default;
  Result := 17;
  for Value in Values do begin
    Result := Result*37 + EqualityComparer.GetHashCode(Value);
  end;
end;

class function TArray.GetHashCode<T>(Values: Pointer; Count: Integer): Integer;
// see https://dev59.com/enI-5IYBdhLWcg3w18V3 and https://dev59.com/_2XWa4cB1Zd3GeqPO6Nr
var
  Value: ^T;
  EqualityComparer: IEqualityComparer<T>;
begin
  EqualityComparer := TEqualityComparer<T>.Default;
  Result := 17;
  Value := Values;
  while Count>0 do begin
    Result := Result*37 + EqualityComparer.GetHashCode(Value^);
    inc(Value);
    dec(Count);
  end;
end;
{$IFDEF OverflowChecksEnabled}
  {$Q+}
{$ENDIF}

2
请不要因为我过于追求细节而杀了我,但是在我看来,第一行真的不应该有冒号。 - Andreas Rejbrand
1
@Andreas,与其批判你,我更愿意称赞你。在我看来,严谨是一种美好而值得追求的品质。 - David Heffernan

2

这个问题涉及到

function MyFunc: TStringDynArray;
begin
  Result := ['Data1', 'Data2'];
end;

['Data1', 'Data2']被解释为集合(set)

我有时会使用以下方便的函数(但通常不在性能关键的部分中使用):

function MakeStringArray(const Strings: array of string): TStringDynArray;
var
  i: Integer;
begin
  SetLength(Result, Length(Strings));
  for i := Low(Strings) to High(Strings) do
    Result[i] := Strings[i];
end {MakeStringArray};

使用这个方法,您可以将第一个示例改写为以下形式:
function MyFunc: TStringDynArray;
....
function MyFunc: TStringDynArray;
begin
    Result := MakeStringArray(['Data1', 'Data2']);
end;

但是,既然你正在使用XE3,最好使用TStringDynArray.Create,就像Andreas Rejbrand建议的那样


是的,关键是你会期望编译器检测返回类型并知道它是一个动态数组。对我来说,这似乎是一个普遍的限制。 - James
1
虽然你说的 ['Data1', 'Data2'] 语法上看起来像是一个集合,但它无法编译通过,因为集合不能以字符串作为它们的“基本类型”。 - Andreas Rejbrand
@AndreasRejbrand:没错。编译器明确提到它需要一个序数类型,所以这个错误是由于复用了[...]符号引起的。这个符号既可以表示集合也可以表示数组。在这种情况下,编译器并不够聪明,无法看出它应该是一个数组而不是一个集合。 - Martijn
在动态数组构造函数出现之前,我们必须如何操作才能实现这一功能。+1 - Andreas Rejbrand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接