【问题标题】:Using "for in" sentence and compiler error E2064使用“for in”语句和编译器错误 E2064
【发布时间】:2011-10-04 07:13:51
【问题描述】:

我想在 D2010 下的测试用例中使用 for in 语句。

如果我想写入 Param.Value 变量,那么编译器会报告错误 2064,但允许从同一记录写入 Param.Edit.text,为什么?

测试用例:

type
//
  TparamSet = (param_A, param_B, param_C, param_D, param_E, param_F);

  TParam = record
    Edit        :TEdit;
    Value       :integer;
  end;

var
  dtcp                  :array [TparamSet] of TParam;

procedure ResetParams;
var
  Param                 :TParam;
  A                     :Integer;
begin
  for Param in dtcp do
  begin
    Param.Edit.text:= 'Test';             //No problem
    A := Param.Value;                     //No problem
    Param.Value := 0;                     //Error: E2064 Left side cannot be assigned to;
  end;
end;

【问题讨论】:

    标签: delphi delphi-2010 for-in-loop


    【解决方案1】:

    记录是值类型。 for in 循环返回数组中每条记录的副本,因此编译器错误实际上是在告诉您修改它是徒劳的。

    您需要使用老式的 for 循环:

    for i := low(dtcp) to high(dtcp) do
      dtcp[i].Value := 0;
    

    【讨论】:

    • +1 - 我希望我能像这里的大卫这样的人一样博学!你真的很了解你的东西:)
    • 这也适用于其 dotNet 通讯员for..each(C# 和 VB)。如果您必须修改,请使用旧时尚 - for..each 变体在这里对您没有帮助。
    • @Fabricio 您也不能在标准 for 循环中修改循环变量。不同之处在于在 for 循环中使用间接的能力。
    • @David:我说的是迭代的集合,而不是循环变量——在 Delphi 编译器强制执行 for 循环变量的不可写性质之前,更改循环变量是错误的代码。 for..each 中的集合在 dotNET 上是只读的。这就是我的观点。
    【解决方案2】:

    函数返回的记录存在一些问题。 (迭代器使用一个函数)。编译器错误可以使用一个额外的变量来解决:

        Param2 := Param;
        Param2.Value := 0;                     // This works (D2010)
    

    但这仍然不会更新 dtcp 数组。这并不奇怪,因为记录被复制并且副本被更新。您可以通过使用类(或指向记录的指针)来解决此问题:

    TParam = class
      Edit        :TEdit;
      Value       :integer;
    end;
    

    现在原始代码可以工作了。但不要忘记创建和销毁类。

    procedure ResetParams;
    var
      Param                 :TParam;
      A                     :Integer;
    begin
      for Param in dtcp do
      begin
        Param.Edit.text:= 'Test';             //No problem
        A := Param.Value;                     //No problem
        Param.Value := 0;                     // This works (D2010)
      end;
    end;
    

    【讨论】:

      【解决方案3】:

      记录是值类型,而不是引用类型。在您的情况下,Param 不是数组中的记录,而是数组中记录的 副本。这就是编译器将其视为常量并阻止您为记录的临时副本赋值的原因。

      【讨论】:

        【解决方案4】:

        记录类型是值类型;对于简单的情况,假设您在TList<TRecordType> 上使用for in 循环。由于迭代器是一个函数,它将返回列表中记录的副本。更改 copy 对列表中的记录没有影响;即使编译器允许你这样做,就像在旧版本的 Delphi 中那样,结果也不会是你所期望的。

        想象一个函数返回一个普通的Integer(也是一个值类型)。你永远不会想到写这样的代码,因为直觉告诉你这没有意义:

        function aFunc(aParam:Integer): Integer;
        begin
          Result := 7;
        end;
        
        procedure Test;
        begin
          aFunc(8) := 9; // This doesn't compile and intuition tells you it's *very* wrong.
        end;
        

        ...但情况与唱片相同。想象一下这个例子:

        type TTestRecord = record
          aField: Integer;
        end;
        
        function TestFunc: TTestRecord;
        begin
          Result.aField := 9;
        end;
        
        procedure TestProc;
        begin
          TestFunc.aField := 10; // This also doesn't make any sense, but intuition might
                                 // fail on this one. Older versions of the compiler allowed this!
        end;
        

        for in 循环的情况与此相同。迭代器本身使用最有可能通过函数实现的属性。由于更改函数的结果几乎没有意义,并且考虑到更改不会传播回迭代器获得其值的任何地方,编译器在阻止它方面做得很好!

        【讨论】:

          【解决方案5】:

          是的,记录是值类型,因此您会收到一份副本,但这不是真正的问题。主要问题是for-in循环返回的值很可能是函数结果,编译器不允许你写入函数结果。例如,这同样适用于属性。

          如果函数结果是引用类型,例如类型化的指针或对象,您可以写入其属性,但不能更改项目本身。如果函数是一个值类型,你也不能写入它的一部分。

          您当然可以将值分配给变量并对其进行更新,但就像其他人所说的那样,您不会修改原始值。

          这一切都意味着 for-in 循环只能用于读取数组、列表、集合、集合等内容,而绝不能写入它们.

          【讨论】:

          • 不是真的!您可以调用类属性(但不能记录)并在我调用时更新它:Param.Edit.text:= 'Test'
          • 哦,您可以拥有返回对对象的引用的属性,然后在该引用上设置属性。但是,您不能更改引用本身,这就是属性返回的内容。正如我所说,属性可以是函数结果。这些不一定有自己的地址,所以你不能给他们写信。 enumerator.Current() 调用的结果也是如此。
          猜你喜欢
          • 1970-01-01
          • 2017-04-07
          • 1970-01-01
          • 2014-05-27
          • 1970-01-01
          • 2015-01-21
          • 2011-05-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多