以下情况:
type
TRec = record
Member : Integer;
end;
TMyClass = class
private
FRec : TRec;
public
property Rec : TRec read FRec write FRec;
end;
以下不起作用(左侧无法分配),这是可以的,因为TRec
是值类型:
MyClass.Rec.Member := 0;
在D2007中,虽然以下内容有效:
with MyClass.Rec do
Member := 0;
不幸的是,它在D2010中不起作用(我认为它在D2009中也不起作用)。第一个问题:为什么?故意改变了吗?或者它只是其他一些变化的副作用? D2007的解决方法只是一个“错误”吗?
第二个问题:您如何看待以下解决方法?使用安全吗?
with PRec (@MyClass.Rec)^ do
Member := 0;
我在这里讨论的是现有代码,因此必须进行的更改才能使其有效。
谢谢!
答案 0 :(得分:6)
这
MyClass.Rec.Member := 0;
不编译是设计的。事实上,两个“有” - 构造的编译是(AFAICT)仅仅是疏忽。所以两者都不“安全使用”。
两个安全的解决方案是:
MyClass.Rec
分配给您操作并分配回MyClass.Rec
的临时记录。TMyClass.Rec.Member
作为财产展开。答案 1 :(得分:3)
在某些类似的情节中,一个班级的记录需要“直接操纵”,我经常使用以下内容:
PMyRec = ^TMyRec;
TMyRec = record
MyNum : integer
end;
TMyObject = class( TObject )
PRIVATE
FMyRec : TMyRec;
function GetMyRec : PMyRec;
PUBLIC
property MyRec : PMyRec << note the 'P'
read GetMyRec;
end;
function TMyObject.GetMyRec : PMyRec; << note the 'P'
begin
Result := @FMyRec;
end;
这样做的好处是,您可以利用Delphi自动解除引用来对每个记录元素进行可读代码访问,即:
MyObject.MyRec.MyNum:= 123;
我无法记住,但也许WITH可以使用这种方法 - 我尽量不使用它! 布赖恩
答案 2 :(得分:1)
无法直接分配的原因是here
对于WITH,它仍然可以在D2009中使用,我本来期望它也可以在D2010中工作(我现在无法测试)。
更安全的方法是直接暴露记录属性,正如艾伦在上面提到的那样SO post:
property RecField: Integer read FRec.A write FRec.A;
答案 3 :(得分:0)
记录是值,它们并不是实体。
他们甚至具有逐个复制的语义!这就是为什么你不能就地改变财产价值的原因。因为它会违反FRec的值类型语义,并且依赖于它的中断代码是不可变的或至少是安全的副本。
他们在这里提出的问题是,为什么你需要一个值(你的TRec)来表现得像一个对象/实体?
如果这是你正在使用它的那个“TRec”不是更合适吗?
我的观点是,当你开始使用超出其意图的语言功能时,你可以很容易地发现自己处于每个米路上必须对抗你的工具的情况。
答案 4 :(得分:0)
它被改变的原因是它是一个编译器错误。它编译的事实并不能保证它能够正常工作。 一旦将Getter添加到属性
,它就会失败unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FPoint: TPoint;
function GetPoint: TPoint;
procedure SetPoint(const Value: TPoint);
{ Private declarations }
public
{ Public declarations }
property Point : TPoint read GetPoint write SetPoint;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
with Point do
begin
X := 10;
showmessage(IntToStr(x)); // 10
end;
with Point do
showmessage(IntToStr(x)); // 0
showmessage(IntToStr(point.x)); // 0
end;
function TForm2.GetPoint: TPoint;
begin
Result := FPoint;
end;
procedure TForm2.SetPoint(const Value: TPoint);
begin
FPoint := Value;
end;
end.
你的代码会突然中断,你会责备Delphi / Borland首先允许它。
如果你不能直接分配一个属性,不要使用hack来分配它 - 它会在某一天咬回来。
使用Brian的建议返回一个指针,但放弃With - 你可以直接做Point.X:= 10;
答案 5 :(得分:-2)
另一种解决方案是使用辅助函数:
procedure SetValue(i: Integer; const Value: Integer);
begin
i := Value;
end;
SetValue(MyClass.Rec.Member, 10);
虽然它仍然不安全(参见Barry Kelly关于Getter / Setter的评论)
/编辑:下面是最丑陋的黑客(也可能是最不安全的),但我发布它真是太好笑了:
type
TRec = record
Member : Integer;
Member2 : Integer;
end;
TMyClass = class
private
FRec : TRec;
function GetRecByPointer(Index: Integer): Integer;
procedure SetRecByPointer(Index: Integer; const Value: Integer);
public
property Rec : TRec read FRec write FRec;
property RecByPointer[Index: Integer] : Integer read GetRecByPointer write SetRecByPointer;
end;
function TMyClass.GetRecByPointer(Index: Integer): Integer;
begin
Result := PInteger(Integer(@FRec) + Index * sizeof(PInteger))^;
end;
procedure TMyClass.SetRecByPointer(Index: Integer; const Value: Integer);
begin
PInteger(Integer(@FRec) + Index * sizeof(PInteger))^ := Value;
end;
它假设记录的每个成员都是(P)整数大小,如果没有则会崩溃。
MyClass.RecByPointer[0] := 10; // Set Member
MyClass.RecByPointer[1] := 11; // Set Member2
您甚至可以将偏移硬编码为常量并直接通过偏移
进行访问const
Member = 0;
Member2 = Member + sizeof(Integer); // use type of previous member
MyClass.RecByPointer[Member] := 10;
function TMyClass.GetRecByPointer(Index: Integer): Integer;
begin
Result := PInteger(Integer(@FRec) + Index)^;
end;
procedure TMyClass.SetRecByPointer(Index: Integer; const Value: Integer);
begin
PInteger(Integer(@FRec) + Index)^ := Value;
end;
MyClass.RecByPointer[Member1] := 20;