Recent

Author Topic: How to use "TPersistent.assign()"  (Read 5967 times)

nana232

  • New Member
  • *
  • Posts: 40
How to use "TPersistent.assign()"
« on: August 19, 2017, 05:27:44 am »
I tested copying my instances (TPersistent) using assign() method but it got error: "Cannot assign a ... "
I dont know what is the corrected way to use it. Anyone please kindly advice me.  ::)

Below is my code with two units:

Code: Pascal  [Select][+][-]
  1. unit fmain;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs
  9.  
  10.   ,umyobj;
  11.  
  12. type
  13.  
  14.   { TForm1 }
  15.  
  16.   TForm1 = class(TForm)
  17.     procedure FormCreate(Sender: TObject);
  18.     procedure FormDestroy(Sender: TObject);
  19.   private
  20.     { private declarations }
  21.   public
  22.     { public declarations }
  23.   end;
  24.  
  25. var
  26.   Form1: TForm1;
  27.   MyObj1,MyObj2:TMyObj;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35. procedure TForm1.FormCreate(Sender: TObject);
  36. begin
  37.   MyObj1:=TMyObj.create;
  38.   MyObj2:=TMyObj.create;
  39.  
  40.   MyObj2.Name:='Obj2';
  41.   MyObj2.MyInt:=2;
  42.  
  43.   MyObj1.Assign(MyObj2);
  44. end;
  45.  
  46. procedure TForm1.FormDestroy(Sender: TObject);
  47. begin
  48.   MyObj1.free;
  49.   MyObj2.free;
  50. end;
  51.  
  52. end.
  53.  


Code: Pascal  [Select][+][-]
  1. unit umyobj;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils;
  9.  
  10. type
  11.  
  12.   { TMyObj }
  13.  
  14.   TMyObj = class(Tpersistent)
  15.     private
  16.       FName:string;
  17.       FMyInt:integer;
  18.     public
  19.       constructor create;
  20.     published
  21.       property Name:String read FName write FName;
  22.       property MyInt:integer read FMyInt write FMyInt;
  23.   end;
  24.  
  25. implementation
  26.  
  27. { TMyObj }
  28.  
  29. constructor TMyObj.create;
  30. begin
  31.   FName:='';
  32.   MyInt:=0;
  33. end;
  34.  
  35. end.
  36.  
Lazarus 1.8.4 Win10 32bit

EganSolo

  • Sr. Member
  • ****
  • Posts: 290
Re: How to use "TPersistent.assign()"
« Reply #1 on: August 19, 2017, 06:31:01 am »
Nana232,

You need to override the AssignTo method of TPersistent into your TMyObj class to tell it how to do the assignment. Something like this might work:
In the declaration section, add:
Code: Pascal  [Select][+][-]
  1.   Protected
  2.      procedure AssignTo(Dest: TPersistent); override;
  3.  

Then when you implement it, write
Code: Pascal  [Select][+][-]
  1. procedure TMyObj.AssignTo(Dest: TPersistent);
  2. var MyDestObj : TMyObj;
  3. begin
  4.    MyDestObj := Dest as TMyObj;
  5.    //you can use a typecast instead as in: MyDestObj := TMyObj(Dest)
  6.   MyDestObj.Name := self.Name; //self is not really required but I find it clearer
  7.   MyDestObj.MyInt := Self.MyInt;
  8. end;
  9.  

Hope this helps.

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: How to use "TPersistent.assign()"
« Reply #2 on: August 19, 2017, 08:52:03 am »
See also
http://lazarus-ccr.sourceforge.net/docs/rtl/classes/tpersistent.assignto.html
Note the above example is not quite correct. Inherited is missing and you should protect it a bit more.
Here an example that is a bit more as it should be done:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mode objfpc}
  3. uses classes,sysutils;
  4. type
  5.   TMyClass = class(TPersistent)
  6.   public
  7.     procedure AssignTo(source:TPersistent);override;
  8.   end;
  9.  
  10.   TMyClass2 = class(Tpersistent);
  11.  
  12.   procedure TmyClass.AssignTo(Source: TPersistent);  
  13.   var
  14.    MyClass : TMyClass;
  15.   begin
  16.     if Source is TmyClass then  // always test with is, not as: that will also fail when it is wrong.
  17.     begin
  18.       MyClass := TmyClass(Source);  // can now hard cast, no soft cast
  19.       // Copy properties here
  20.     end
  21.     else if Source = nil then  // this is because nil is actually valid...
  22.      Self := Default(TMyClass);// initializes everything to default values
  23.    else
  24.      // important
  25.       inherited; // this will raise an EConvertError exception. If you leave out this you will get a crash..
  26.    end;
  27. // example code
  28. var
  29.   a,b:TMyClass;
  30.   c:TMyclass2;
  31. begin
  32.   a:= TMyClass.Create;
  33.   b:= TMyClass.Create;
  34.   try
  35.     try
  36.       a.Assign(b);
  37.       writeln('Ok');
  38.     except
  39.       on e:EConvertError do
  40.         Writeln(e.Message) else raise
  41.     end;
  42.   finally
  43.     b.free;
  44.     a.free;
  45.   end;
  46.   a:= TMyClass.Create;
  47.   c := TMyClass2.Create;
  48.   try
  49.     try
  50.       a.Assign(c);
  51.       writeln('Ok');  // will never be reached, exception will be raised.
  52.     except
  53.       On e:EConvertError do
  54.         writeln(e.message) else raise
  55.     end;
  56.   finally
  57.     c.free;
  58.     a.free;
  59.   end;
  60. end.
« Last Edit: August 19, 2017, 10:19:33 am by Thaddy »
Specialize a type, not a var.

Akira1364

  • Hero Member
  • *****
  • Posts: 561
Re: How to use "TPersistent.assign()"
« Reply #3 on: August 20, 2017, 06:40:55 am »
Basically you just need to make sure that Assign is always overridden in your TPersistent-descendant classes, or at least that you've implemented your own exceptions for cases where it's not. Never call the "inherited" version under any circumstances, as it will raise the default "Cannot blah blah" exception 100% of the time. The main purpose of TPersistent is the RTTI for published properties, by the way, and not the Assign method.
« Last Edit: August 20, 2017, 07:06:30 am by Akira1364 »

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: How to use "TPersistent.assign()"
« Reply #4 on: August 20, 2017, 08:42:18 am »
@Akira1364
Well, you need to call inherited if the class comparison fails. That is by design. See my example. You should not implement your own exception for the EConvertError, because that is raised through inheritance. Then you would duplicate code...
« Last Edit: August 20, 2017, 08:51:39 am by Thaddy »
Specialize a type, not a var.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: How to use "TPersistent.assign()"
« Reply #5 on: August 20, 2017, 02:52:11 pm »
Nana232,

You need to override the AssignTo method of TPersistent into your TMyObj class to tell it how to do the assignment. Something like this might work:
In the declaration section, add:
Code: Pascal  [Select][+][-]
  1.   Protected
  2.      procedure AssignTo(Dest: TPersistent); override;
  3.  

Then when you implement it, write
Code: Pascal  [Select][+][-]
  1. procedure TMyObj.AssignTo(Dest: TPersistent);
  2. var MyDestObj : TMyObj;
  3. begin
  4.    MyDestObj := Dest as TMyObj;
  5.    //you can use a typecast instead as in: MyDestObj := TMyObj(Dest)
  6.   MyDestObj.Name := self.Name; //self is not really required but I find it clearer
  7.   MyDestObj.MyInt := Self.MyInt;
  8. end;
  9.  

Hope this helps.
Although that will work it is not recommended. AssignTo is to be used strictly for object outside you hierarchy and as a result outside your control.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: How to use "TPersistent.assign()"
« Reply #6 on: August 20, 2017, 04:33:35 pm »
Although that will work it is not recommended. AssignTo is to be used strictly for object outside you hierarchy and as a result outside your control.
AssignTo is strictly *within* the object hierarchy.
So why do both Embarcadero and the Freepascal manuals say you may override AssignTo instead of Assign (certainly in the case of new objects). The difference is not very big, but noticable
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Classes_TPersistent_AssignTo.html
http://lazarus-ccr.sourceforge.net/docs/rtl/classes/tpersistent.assignto.html

In case you didn't know: TPersistent.Assign calls TPersistent AssignTo...

Note it is possible to override TPersistent.Assign itself, but that is for the reasons mentioned in the docs not the best option since I understand this to be about newly created objects.

In both cases my example code applies.
« Last Edit: August 20, 2017, 05:20:08 pm by Thaddy »
Specialize a type, not a var.

Akira1364

  • Hero Member
  • *****
  • Posts: 561
Re: How to use "TPersistent.assign()"
« Reply #7 on: August 20, 2017, 09:20:26 pm »
@Akira1364
Well, you need to call inherited if the class comparison fails. That is by design. See my example. You should not implement your own exception for the EConvertError, because that is raised through inheritance. Then you would duplicate code...

I was moreso saying that you should check the class types before you call Assign at all, and depending on the nature of the application and whether it's "safe" to do so relative to the structure of the code, either silently exit the method that calls Assign at that point or raise your own exception with a more detailed message about what went wrong.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: How to use "TPersistent.assign()"
« Reply #8 on: August 20, 2017, 09:34:57 pm »
Although that will work it is not recommended. AssignTo is to be used strictly for object outside you hierarchy and as a result outside your control.
AssignTo is strictly *within* the object hierarchy.
So why do both Embarcadero and the Freepascal manuals say you may override AssignTo instead of Assign (certainly in the case of new objects). The difference is not very big, but noticable
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Classes_TPersistent_AssignTo.html
http://lazarus-ccr.sourceforge.net/docs/rtl/classes/tpersistent.assignto.html

In case you didn't know: TPersistent.Assign calls TPersistent AssignTo...
No it does not. you should check your facts before posting here is the code.
Code: Pascal  [Select][+][-]
  1. procedure TPersistent.Assign(Source: TPersistent);
  2. begin
  3.   If Source<>Nil then
  4.     Source.AssignTo(Self)
  5.   else
  6.     AssignError(Nil);
  7. end;
  8.  

It calls the source assignto and the source might be in the current object hierarchy or might not So it is out of lack that it works.

Note it is possible to override TPersistent.Assign itself, but that is for the reasons mentioned in the docs not the best option since I understand this to be about newly created objects.
In both cases my example code applies.
wrong again. The documentation clearly states that you should override assign unless you want to copy your self to other control types.
So in sort copy from others in to your control override assign. Copy your control in to others override assignto.
« Last Edit: August 20, 2017, 09:38:09 pm by taazz »
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

 

TinyPortal © 2005-2018