当泛型遇上结构体...

一般使用泛型都是放类啊, 基础类型什么的, 所以使用上和非泛型基本没什么区别, 不过昨天有人在群里问的问题到是暴露了一个特例:

  TRC = record
    Str: string;
    Int: Integer;
  end;

...

var
  nRC: TRC;
  nList: TList<TRC>;
  i: Integer;
begin
  nList := TList<TRC>.Create;
  for i := 0 to 9 do
  begin
    with nRC do
    begin
      Str := IntToStr(i);
      Int := i;
    end;
    nList.Add(nRC);
  end;

  nList[3].Str := 'ABC'; {这里会提示左边不允许赋值}

  nList.Free;
end;

显然, 如果不是一个结构体而是一个类型就没问题了, 先不说原因, 这里我先列出思路:

首先编译器不允许赋值, 那么如果是属性呢?

  TRC = record
  private
    FStr: string;
  public
    Int: Integer;
    property Str: string read FStr write FStr;
  end;

改成这样...可惜仍然不行, 继续

  TRC = record
  private
    FStr: string;
    function GetStr: string;
    procedure SetStr(const Value: string);
  public
    Int: Integer;
    property Str: string read GetStr write SetStr;
  end;

...

function TRC.GetStr: string;
begin
  Result := FStr;
end;

procedure TRC.SetStr(const Value: string);
begin
  FStr := Value;
end;

...

var
  nRC: TRC;
  nList: TList<TRC>;
  i: Integer;
begin
  nList := TList<TRC>.Create;
  for i := 0 to 9 do
  begin
    with nRC do
    begin
      Str := IntToStr(i);
      Int := i;
    end;
    nList.Add(nRC);
  end;

  ShowMessage(nList[3].Str);
  nList[3].Str := 'ABC';
  ShowMessage(nList[3].Str);
  nList.Free;
end;

这样, 编译器终于不报错了, 可惜赋值完全无效...2次的Showmessage都是3

OK, 现在开始分析一下具体是怎么执行的

Unit1.pas.54: nList[3].Str := 'ABC';
004B4C74 8D4DE8           lea ecx,[ebp-$18]
004B4C77 BA03000000       mov edx,$00000003
004B4C7C 8BC6             mov eax,esi
004B4C7E E83D0F0000       call Generics + $4B5BC0
004B4C83 8D45E8           lea eax,[ebp-$18]
004B4C86 BA204D4B00       mov edx,$004b4d20
004B4C8B E8B0000000       call Generics + $4B4D40

显然, 一开始先吧list里的内容复制出来一份临时数据, 然后给这个临时数据赋值, 然后...就没有然后了

只是改变的临时数据, list里的内容完全没变化啊

于是, 瞬间联想到, 如果一个类实例里的某一属性是结构体的话, 那么对其赋值和取值, 也是基于创建一个临时变量,修改其内容再全部替换的过程

这样就好解释多了, 看来是不能直接修改结构体的单独一项, 那咱们换个方法

  TRC = record
    Str: string;
    Int: Integer;
  end;

...

var
  nRC: TRC;
  nList: TList<TRC>;
  i: Integer;
begin
  nList := TList<TRC>.Create;
  for i := 0 to 9 do
  begin
    with nRC do
    begin
      Str := IntToStr(i);
      Int := i;
    end;
    nList.Add(nRC);
  end;

  ShowMessage(nList[3].Str);
  nRC := nList[3];
  nRC.Str := 'ABC';
  nList[3] := nRC;
  ShowMessage(nList[3].Str);
  nList.Free;
end;

这样果然没问题了

至于结构体为啥不能再类实例里直接修改其内部元素的问题, 这个有待继续研究...

原文地址:https://www.cnblogs.com/lzl_17948876/p/3579698.html