AlanScottAgain 1 Posted June 20, 2022 Hi I don't understand why I'm getting this message: This a cut down version of my object. TDrawingObject = class private FObjectType: TDrawingObjectType; FBoundsRect: TRectF; FTag: Integer; public constructor Create(AObjectType: TDrawingObjectType); destructor Destroy; property ObjectType: TDrawingObjectType read FObjectType write FObjectType; property BoundsRect: TRectF read FBoundsRect write FBoundsRect; property Tag: Integer read FTag write FTag default 0; end; Then in procedure: var TempDrawingObject: TDrawingObject; begin TempDrawingObject:= TDrawingObject.Create; TempResult.BoundsRect.Top:= 20; TempResult.Tag:= 5; end; I can not assign 20 to the BoundsRect.top Property. But I can assign to the Tag property. Do I need to create a setter? What have I missed. Many thanks for your advice. Alan Share this post Link to post
Der schöne Günther 316 Posted June 20, 2022 (edited) Your variable FBoundsRect is of type TRectF which is a record. Records are passed by value, not by reference. Meaning: When you access TempResult.BoundsRect, you are not acting on the original data, but a copy on it. Yes, in theory, you can access and modify the temporary data, but the compiler does not let you as it makes no sense to do so. The compiler message is unfortunately, once again, not helpful. One solution is storing it in a temporary local variable, and then assigning it back, after you have changed the bounds rect. Another one is getting rid of the the properties altogether as they just add noise and hide the fact that you can just replace the complete record, but not just parts of it, as you desire. program Project1; uses System.SysUtils, System.Types; type TMyObject = class(TObject) strict private var _PropField: TRectF; public property PropField: TRectF read _PropField write _PropField; public var VarField: TRectF; end; var myObject: TMyObject; begin myObject := TMyObject.Create(); myObject.VarField.Left := 12.0; myObject.PropField.Left := 12.0; // E2064 LEft side cannot be assigned to end. Edited June 20, 2022 by Der schöne Günther Share this post Link to post
AlanScottAgain 1 Posted June 21, 2022 Thank you for the clear explanation. Share this post Link to post
AlanScottAgain 1 Posted June 21, 2022 So for simple cases don't bother with a property? Only when you need a getter/setter? Share this post Link to post
Der schöne Günther 316 Posted June 21, 2022 (edited) In my absolutely personal opinion, never bother with properties at all. There is no real use for them. They have a lot of other disadvantages - You can't pass them as var or out parameters, Code completion will not tell you if a property is writeable or not. Many other languages are perfectly fine with no properties at all. I can think of C# properties, but they are capable of so much more. In Delphi, a getter/setter makes it immediately clear if a value can be read or written, makes it obvious that there could be side effects as it's not just a regular variable, and can be stored in a function pointer. Also, you can make the setter protected and only allow yourself and your subclasses or another interface to change the value. None of this is possible with properties. In my opinion, they just get in the way and require even more typing as Delphi is already incredibly verbose. Edited June 21, 2022 by Der schöne Günther Share this post Link to post
David Heffernan 2345 Posted June 21, 2022 8 minutes ago, Der schöne Günther said: You can't pass them as var or out parameters 9 minutes ago, Der schöne Günther said: In Delphi, a getter/setter makes it immediately clear if a value can be read or written These two statements appear in conflict with each other. Advocacy for using getter/setter methods directly, flies in the face of criticism that properties can't be passed as var or out params. Neither can methods. Share this post Link to post
Der schöne Günther 316 Posted June 21, 2022 That they cannot be passed as var or out was more a comparison to simple fields, I should have made that clearer. Meaning: If you don't need getter/setters, I fail to see why you would ever go with properties at all. Share this post Link to post
Stano 143 Posted June 21, 2022 34 minutes ago, Der schöne Günther said: Meaning: If you don't need getter/setters, I fail to see why you would ever go with properties at all. Such a simple example: I need to store or retrieve the PrimaryKey value there. Why would Setter / Getter be in such a simple case? Share this post Link to post
David Heffernan 2345 Posted June 21, 2022 1 hour ago, Der schöne Günther said: That they cannot be passed as var or out was more a comparison to simple fields, I should have made that clearer. Meaning: If you don't need getter/setters, I fail to see why you would ever go with properties at all. So instead of code like obj.foo := obj.bar + 10; you prefer obj.setFoo(obj.getBar() + 10); or perhaps obj.setFoo(obj.getBar + 10); (note that I personally am not keen on being able to call a function without the parens because it leads to ambiguity for type inference) Share this post Link to post
Fr0sT.Brutal 900 Posted June 21, 2022 1 hour ago, Der schöne Günther said: In my opinion, they just get in the way and require even more typing as Delphi is already incredibly verbose. Yep, sure. Especially if you need to make some internal field accessible as-is I had some light experience with QT that has no properties but only g/setters. That were really awful hours. Share this post Link to post
PeterBelow 238 Posted June 21, 2022 2 hours ago, Der schöne Günther said: That they cannot be passed as var or out was more a comparison to simple fields, I should have made that clearer. Meaning: If you don't need getter/setters, I fail to see why you would ever go with properties at all. You can change the visibility of properties inherited from an ancestor, you cannot do that with fields. The VCL itself makes heavy use of that. Share this post Link to post
dummzeuch 1505 Posted June 21, 2022 3 hours ago, Der schöne Günther said: In my absolutely personal opinion, never bother with properties at all. There is no real use for them. ... unless they are published and used via RTTI, e.g. in the Object Inspector or in some serialization code. Apart from that I partly agree. Especially the value of readonly properties is nearly zero and for interfaces they are a pain in the lower back. Share this post Link to post
Anders Melander 1782 Posted June 21, 2022 3 hours ago, Der schöne Günther said: never bother with properties at all. There is no real use for them. Bold statement, given the fact that they were one of the language features, if not the feature, that made Delphi possible in the first place. 3 hours ago, Der schöne Günther said: You can't pass them as var or out parameters, Code completion will not tell you if a property is writeable or not. If you need to pass them as var or out parameters then I would say that there's something wrong with your design but without a concrete example it's hard to say what. Code completion: Ditto. If you need to modify a property, which isn't writable, then there's something wrong with your design or with the class design. 3 hours ago, Der schöne Günther said: Many other languages are perfectly fine with no properties at all. Many other languages doesn't have objects, interfaces, exception handling, etc. Pretty irrelevant. Share this post Link to post
Anders Melander 1782 Posted June 21, 2022 12 minutes ago, dummzeuch said: Especially the value of readonly properties is nearly zero So use a function instead but I can't see how that would improve anything for you. A property better expresses the intent. 15 minutes ago, dummzeuch said: for interfaces they are a pain in the lower back For interfaces properties are purely syntactic sugar so you really don't have to use them if you prefer not to. You can just call the getters or setters directly. How is that a pain? Share this post Link to post
Fr0sT.Brutal 900 Posted June 21, 2022 2 hours ago, dummzeuch said: Especially the value of readonly properties is nearly zero TSomeClass private FFoo: string; public // with props property Foo: string read FFoo; // without props function Foo: string; ... function Foo: string; begin Result := FFoo; end; Share this post Link to post
dummzeuch 1505 Posted June 21, 2022 29 minutes ago, Fr0sT.Brutal said: 3 hours ago, dummzeuch said: Especially the value of readonly properties is nearly zero TSomeClass private FFoo: string; public // with props property Foo: string read FFoo; // without props function Foo: string; ... function Foo: string; begin Result := FFoo; end; OK, I should have explicitly said "readonly properties with a getter method", because that's what I meant. Share this post Link to post
dummzeuch 1505 Posted June 21, 2022 2 hours ago, Anders Melander said: 3 hours ago, dummzeuch said: and for interfaces they are a pain in the lower back. For interfaces properties are purely syntactic sugar so you really don't have to use them if you prefer not to. You can just call the getters or setters directly. How is that a pain? Because once these properties exist in an interface, every class that implements the interface must also declare them. For a class at least properties are usually public/published while their getter/setter methods are private/protected, so one could argue that using a property reduces clutter. For an interface both the property and the getter/setter methods are public, so that argument doesn't apply. Share this post Link to post
Anders Melander 1782 Posted June 21, 2022 3 minutes ago, dummzeuch said: Because once these properties exist in an interface, every class that implements the interface must also declare them. Nope: type IFoo = interface function GetBar: IBar; procedure SetBar(const Value: IBar); property Bar: IBar read GetBar write SetBar; end; TFoo = class(..., IFoo) private function GetBar: IBar; procedure SetBar(const Value: IBar); // Look ma, No property! end; begin var Foo: IFoo := TFoo.Create; var Bar := Foo.Bar; end; 8 minutes ago, dummzeuch said: For an interface both the property and the getter/setter methods are public, so that argument doesn't apply. Sure, but I don't see the problem; Don't use them if you don't like them. You will still need the getters and setters so there's not much saved by omitting the property declaration and you lose the meta information that the property communicates. I too would wish the the getters and setters could be hidden from the public API of the interface (like I wish that the private part could be hidden from class declarations) but I guess that we have their history (as COM interfaces) to thank for that. Since I need both COM and non-COM interfaces and a new breed of interfaces would just be a mess I can live with the annoyances the versatility brings with it. Share this post Link to post
dummzeuch 1505 Posted June 21, 2022 (edited) 2 hours ago, Anders Melander said: Nope: type IFoo = interface function GetBar: IBar; procedure SetBar(const Value: IBar); property Bar: IBar read GetBar write SetBar; end; TFoo = class(..., IFoo) private function GetBar: IBar; procedure SetBar(const Value: IBar); // Look ma, No property! end; begin var Foo: IFoo := TFoo.Create; var Bar := Foo.Bar; end; Wow, learned something new today. Quote 3 hours ago, dummzeuch said: For a class at least properties are usually public/published while their getter/setter methods are private/protected, so one could argue that using a property reduces clutter. For an interface both the property and the getter/setter methods are public, so that argument doesn't apply. Sure, but I don't see the problem; Don't use them if you don't like them. I am not talking about interfaces that I design, but those I have to use (e.g. in the ToolsAPI or 3rd party libraries). Not much choice there. My own interfaces don't get properties. They usually have a getter that's called like the property would be (so MyValue rather than GetMyValue) and a setter with the standard name (SetMyValue). Edited June 21, 2022 by dummzeuch Share this post Link to post
Remy Lebeau 1394 Posted June 21, 2022 (edited) On 6/20/2022 at 1:39 AM, AlanScottAgain said: TempResult.BoundsRect.Top:= 20; Other people have already explained why that doesn't work. But, if you really want it to make it work, you could declare your property as returning a PRectF pointer instead, eg: type TDrawingObject = class private ... FBoundsRect: TRectF; ... function GetBoundsRect: PRectF; public ... property BoundsRect: PRectF read GetBoundsRect; ... end; function TDrawingObject.GetBoundsRect: PRectF; begin Result := @FBoundsRect; end; Edited June 22, 2022 by Remy Lebeau Share this post Link to post
Anders Melander 1782 Posted June 21, 2022 25 minutes ago, dummzeuch said: I am not talking about interfaces that I design, but those I have to use (e.g. in the ToolsAPI or 3rd party libraries). Not much choice there. True, if you have to implement an interface declared by someone else - but since you don't have to actually declare the property in your implementation there's zero difference from your perspective between an interface with and one without property declarations. Anyway, to each their own, free world and all. IMO both approaches are just fine. Share this post Link to post
Anders Melander 1782 Posted June 21, 2022 28 minutes ago, Remy Lebeau said: you could declare your property as returning a PRectF pointer instead Some of the code I'm currently maintaining ironically has widespread use of dynamic arrays of very large records. Ironically because it was done to avoid the overhead of lists of objects in order to improve performance. Instead it now struggles with the overhead of dynamic array reallocation and records being copied back and forth. I'm slowly refactoring it to use lists and objects but meanwhile I'm using the approach you're suggesting (minus the copy/paste bug 🙂), so this: type THumongousRec = record Foo: integer; ... end; THugeArray = array of THumongousRec; TMyClass = class private FData: THugeArray; protected function GetData(Index: integer): THumongousRec; procedure SetData(Index: integer; const Value: THumongousRec); public property Data[Index: integer]: THumongousRec read GetData write SetData; end; function TMyClass.GetData(Index: integer): THumongousRec; begin Result := FData[Index]; end; procedure TMyClassSetData(Index: integer; const Value: THumongousRec); begin FData[Index] := Value; end; begin var Data := MyClass.Data[0]; Data.Foo := 1; MyClass.Data[0] := Data; end; becomes this: type THumongousRec = record Foo: integer; ... end; PHumongousRec = ^THumongousRec; THugeArray = array of THumongousRec; TMyClass = class private ...same as before... protected ...same as before... function GetPData(Index: integer): PHumongousRec; public ...same as before... property PData[Index: integer]: PHumongousRec read GetPData; end; function TMyClass.GetPData(Index: integer): PHumongousRec; begin Result := @FData[Index]; end; begin MyClass.PData[0].Foo := 1; end; Of course this only works when the property setter doesn't have side effects. Share this post Link to post