1. 起源
此問題源於[秋風人事檔案管理系統]用Delphi XE重編譯中所發現。
快十年了,當初Delphi 7所編寫項目,想用Delphi XE重新編譯,並打算做為Free軟件發布,編譯錯誤中發現此問題,感覺頗有些意思,遂記錄下來,以做備忘。
自Delphi 2009之後,轉做c#之WinForm界面開發,Delphi 2010之後未實際做過項目,因此至此才遇到此問題。
此時Delphi XE快快已至XE10版。因情結而不願XE2之後的use方法,遂決定以Delphi XE做為工具,重整老項目。
而此問題,簡而言之,就是當記錄體(record)做為屬性出現時,其賦值問題。
2. 賦值
比如,我有一記錄體屬性如下(這裡以TPoint說明問題,實際項目中為自定義記錄體):
type TForm1 = class(TForm) btnTest: TButton; procedure btnTestClick(Sender: TObject); private { Private declarations } FPoint: TPoint; public { Public declarations } property Point: TPoint read FPoint write FPoint; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.btnTestClick(Sender: TObject); begin //Point.X := 12; //如此賦值不成功,報錯為Left side cannot be assigned to //Point.Y := 12; with Point do begin X := 12; Y := 12; end; ShowMessage(Format('x: %d, y: %d', [Point.X, Point.Y])); end;
直接賦於做為屬性的record值,比如Point.X := 12,編譯都不能通過。我們可以理解為面向對象封裝問題不允許操作其內部數據,因為記錄體為值類型。
但事有折中,借with語句,即可方便賦值。所以在以往項目中,多用此寫法,簡潔而方便,而這種寫法在Delphi 2009及之前版本,都支持,可能是Delphi編譯器的BUG吧!
如果是,我喜歡這個BUG,它簡潔了我的寫法,特別是屬性有多級嵌套時。
3. 不再支持
Delphi命運多舛,像個不能決定自己命運的小姑娘一樣被賣來賣去。工作原因換了開發工具,十多年的Delphi開發從此擱置,不曾想其間多少變故,如今再用,細微變化已別於昔時。
如上面代碼,用with語句賦值,也行不通了,報錯為[DCC Error] Unit1.pas(34): E2064 Left side cannot be assigned to.
baidu看有沒有人類似疑惑?可能國內用Delphi漸少原因吧,竟沒找到類似問題,於是stackoverflow一下,果然碰到一根筋的難兄難弟:
Left side cannot be assigned for a record type
Delphi “E2064 Left side cannot be assigned to” error appeared when upgrading a project from 2009 to XE
Delphi 2010+ and “Left side cannot be assigned to” in read-only records: can this be disabled?
看諸回復,大意是Delphi 2010及以後版本其編譯器檢查比以前更為嚴格,其中還有人感謝這樣更改。討論比較熱烈,說啥的都有。日!
與提問者一樣,我只想簡單地做為屬性以記錄需的的數據,僅此而已。這是一個完全有效的語言特性,就這樣給干了,這是歷史的退步!
4. 解決方法
這條路關閉了,總還有其它路,是不是?編譯器想必真是處理屬性為Get、Set方式,而加以特別處理?
總結下吧!如果還想用此方法,就折中下:
a. 直接換屬性為字段,即
property Point: TPoint read FPoint write FPoint;
換為Point: TPoint;
這條方法違背於面向對象封裝,不好,但能用。
b. 若是自定義record,可換為class,這思路中。
c. 以指針方式去賦值:
procedure TForm1.btnTestClick(Sender: TObject); begin with PPoint(@Point)^ do begin X := 12; Y := 12; end; ShowMessage(Format('x: %d, y: %d', [Point.X, Point.Y])); end;
我個人傾向於這個。自定義記錄體,再定義個指向它的指針結構,就可以了。
d. 以臨時變量代之,反賦回去:
procedure TForm1.btnTestClick(Sender: TObject); var p: TPoint; begin with p do begin X := 12; Y := 12; end; Point := p; ShowMessage(Format('x: %d, y: %d', [Point.X, Point.Y])); end;
這應該是正統寫法,但總感覺繁瑣令人難受。
反正是少了之前版本那灑脫淋漓簡潔隨意的寫法,蛋疼之極也!