Delphi Classes: Property vs Get/Set methods - class

So I'm kinda new to OO programming. Delphi has properties which are intended to be a "more elegant" way to acess class data than getters/setters (read it here usage of property vs getters/setters in business classes).
When should I use the fields directly and when should I use getters/setters inside the property? I'm gessing only when the data needs to be manipulated, but I'm not sure.
Edit:
Is it wrong to omit a setter that does nothing but return the value of the field itself?
property Field :integer read FField write FField;

Why properties?
For starters, a quick summary from the documentation on properties:
A property, like a field, defines an attribute of an object. But while a field is merely a storage location whose contents can be examined and changed, a property associates specific actions with reading or modifying its data. Properties provide control over access to an object's attributes, and they allow attributes to be computed.
Why not only setter and getter?
Separation of storage and access can indeed be achieved by the use of just getters and setters, and leave the property be. That is true, but the question you link to originates from a language difference: Delphi does have properties, and the answers there already explain why to use them. The two most obvious reasons are (1) cleaner code and (2) assignment capability. I think this answer deals with it quite extensive already.
Furthermore, without the use of properties, getters and setters are always needed, where with properties they are not. Assume a setter implementation, but no getter: a property can directly read the field.
Class completion
When you just declare a property's name and its type, Delphi's class completion defaults to reading a private field and a private setter which sets the private field. Mind that this is just the default configuration, which you again can modify to your needs. When you fully specify the property declaration, class completion will comply and add a private field, a getter and/or setter as required for your declaration.
Properties without getter and setter
Is it wrong to omit a setter that does nothing but return the value of the field itself?
When a property has no getter nor setter and it just reads and writes the field, then you could conclude that there is no difference beside being consistent. But that is not the case. The field and the property have distinct names, and therefore possibly distinct meaning. Meaning that you can give. See Using properties instead of fields in class methods of the same unit is a bad practice?.
When to use a getter or setter?
... I'm gessing only when the data needs to be manipulated ...
Well, that is partly true. Manipulation is one of the many reasons. Consider a Price property of type String, and its private field FPrice:
Limitation: When the price needs to be equal or higher than zero,
Delegation: When FPrice is part of another field, or when it otherwise is beyond the responsibility of this class,
Verification: When the price may only have two decimals behind the comma,
Interpretation: When the price is entered in thousands, but should be stored in cents,
Effect: When the price has consequences for another field, for example rate or margin,
Activation: When an edit to the price requires immediate action, for example updating the GUI,
Conversion: When the price is entered in dollars but should be stored in yen,
Cancelation: When the price does not make sense, for example when it is entered in scientific notation.
Note that a Price property is quite rudimentary. Leaving its setter or getter for future implementation is very well possible. But imagine more advanced properties which cannot do without setter or getter:
A field that needs to be created before consulting:
function TMyObject.GetBarelyUsed: TRare;
begin
if FBarelyUsed = nil then
FBarelyUsed := TRare.Create(Self);
Result := FBarelyUsed;
end;
An item can be selected, but the item itself does not know what to do. Instead the owner does. Notice the complete absence of the private field in this case:
procedure TItem.SetSelected(Value: Boolean);
begin
if Value <> Selected then
begin
if Value then
Owner.Selection.Add(Self)
else
Owner.Selection.Remove(Self);
end;
end;
An image control, specialized in viewing your own image format. Assignment of the FileName property involves: checking the correct file extension, checking file existance, storing the file name in the private field, loading the file, adjusting the pictures' dimensions, or else undoing the previous assignment:
procedure TAwDxfImage.SetFileName(const Value: TFileName);
begin
if FFileName <> Value then
if SameText(ExtractFileExt(Value), '.' + SDxf) and
FileExists(Value) then
begin
FFileName := Value;
FGraphic.LoadFromFile(FFileName);
FGraphic.SetBounds(Width, Height);
end
else
begin
FFileName := '';
FGraphic.Clear;
end;
end;
Source: NLDelphi

In addition to #NGLN answer there is another use case for property getter/setter.
Accessing class instances through interface is only possible via instance methods. If you have to access property in such case you have to implement getter/setter methods.
type
IField = interface
function GetField: integer;
procedure SetField(value: integer);
property Field: integer read GetField write SetField;
end;
TField = class(TInterfacedObject, IField)
protected
FField: integer;
function GetField: integer;
procedure SetField(value: integer);
public
property Field: integer read GetField write SetField;
end;
var
f: IField;
x, n: integer;
...
f := TField.Create;
f.Field := 5;
f.SetField(6);
n := f.Field;
x := f.GetField;
Of course, depending whether you need only read or write access to that property you can omit setter or getter in your interface declaration.
Keep in mind that accessing instance through interface gives all interface implemented methods public visibility. That is why in above example you can call f.GetField despite it being declared protected (or even private).

What is unacceptable is to do this
TMyClass = class
private
public
Fubar :integer;
end;
the remainder of your examples are fine. I would be happy to receive code from you that reads like this
TMyClass = class
private
FFu : integer;
public
property Fu :integer read FFu write FFu;
end;
because I can confidently change it to
TMyClass = class
private
FFu : integer;
procedure SetFu(Value : Integer);
function GetBar() : string;
public
property Fu :integer read FFu write SetFu;
property Bar : String read GetBar;
end;
without breaking existing code.
I have a personal dislike of setters that do nothing
procedure TMyClass.SetFu(Value : Integer);
begin
FFu := Value;
end;
But in truth it is harmless.
Does that help?
This would be a valid reason or incentive for using a setter
procedure TMyClass.SetFu(Value : Integer);
begin
if FFu <> Value then begin
FFu := Value;
if Assigned(FAfterFooChanged) then
FAfterFooChanged(FFu);
end;
end;
not 'manipulation' as such ...

Related

How do we create custom getters and setters, and what’s the advantage of doing so in Flutter?

What is the reason of using custom getters and setters in an application.
That's fairly very simple
First let me show you a sample of how getters and setters in Dart look like, which is essentially the language behind Flutter
class Foo {
// Creating a field/instance variable
String _fooName; //Keeping it private always
// Using the getter
String get foo_name {
//We can do something else here, like saving the variable somewhere and then returning it to the caller function
return _fooName;// private variable return for use in outside class
}
// Using the setter method
set foo_name (String name) {
// We can do something else, like update another variable based on fooName
this._fooName = name;//private variable being assigned new value
}
}
From the name, setters are involved in setting the values to an instance variable in an object oriented programming paradigm whereas getters are involved in getting the value of an instance variable
Now you would ask why not return the instance variable directly and why having such a roundabout approach to setting and getting the value
Well the answer is while getting as well as setting, we might want to do some other operation too other than just setting or getting the value and it's always better not to give admin access to the variables and that's why they are private so as to promote consistency within the objects accessing the field
It's a matter of preference, but you really shouldn't needlessly create one for a single field
https://dart.dev/guides/language/effective-dart/usage#dont-wrap-a-field-in-a-getter-and-setter-unnecessarily
One use case for creating a setter would be to perform some type of validation
For a getter, it'd be useful for a calculated field based on other properties, rather than a single property alone

Why is this code working and how is Delphi instantiating a class in this case?

I'm doing the Delphi track at exercism, and following how Delphi generates code for a form, answered one of the basic questions like this:
unit uLeap;
interface
type
TSYear = class
public
{ public declarations here }
function isLeap(y: integer): boolean;
end;
var
TYear: TSYear;
implementation
function TSYear.isLeap(y: integer): boolean;
begin
result := ((y mod 4) = 0) and (((y mod 400) = 0) or ((y mod 100) <> 0));
end;
end.
the code compiles without a single complaint, I can run it step by step, and the "isLeap" function is called from another unit several times this way:
procedure YearTest.year_divisible_by_4_not_divisible_by_100_leap_year;
begin
assert.IsTrue(TYear.IsLeap(1996), 'Expected ''true'', 1996 is a leap year.');
end;
...
I've never explicitly created the instance of the class, but it seems as if Delphi is doing it somewhere, maybe when declaring TYear? Is that a valid way?
Despite passing all the tests the code was rejected because it isn't done the conventional way. I'll surely end up doing it differently to have it accepted, but, besides the bad naming, why is this working? Would this code cause problems somewhere I can't see in this simple example?
I've never explicitly created the instance of the class, but it seems as if Delphi is doing it somewhere, maybe when declaring TYear?
No, Delphi is NOT automatically creating an instance of you. When you declare a variable of a class type, it is simply a pointer variable that can be made to point at a valid instance. But you must always create this instance yourself, and save the pointer in the variable:
SYear := TSYear.Create; // create a `TSYear` object and save its address in `SYear`
Is that a valid way?
No.
[W]hy is this working?
Because you are lucky: The isLeap function doesn't access any fields on the class instance.
Would this code cause problems somewhere I can't see in this simple example?
If the function had been using any fields in the class instance, you would have ended up with an AV if lucky and memory corruption if unlucky.
The solution is either to create an instance and use it:
SYear := TSYear.Create;
try
ShowMessage(BoolToStr(SYear.IsLeap(2000), True));
finally
SYear.Free;
end;
Or, since you clearly don't need any instance variables to determine if a year is a leap year or not, it is better to make this a class method:
type
TSYear = class
public
class function IsLeap(AYear: Integer): Boolean; static;
end;
This way, it can be called without any class instance: TSYear.IsLeap(2000). Notice that TSYear is the class (type) name, not a variable of this type.
Please see the documentation for a great conceptual introduction to all these concepts.

DXE7: "type A = type B" and var x (of type A):= A.create leads to E2010 incompatible type compile error. Why?

A. Short summary of the problem:
type
A = class(TObject)
end;
B = type A;
The compiler error E2010 incompatible types: 'B' and 'A' is shown, when you instantiate a variable of class B in the following style:
var
TheB: B;
begin
TheB:= B.Create;
..
I can avoid that problem by removing the second "type", so the declaration is the standard one:
type
A = class(TObject)
end;
B = A;
But in my opinion, the error should not come, even WITH the second "type", because A is not directly used (The second "type" tells the compiler to see both classes as individuals, see http://docwiki.embarcadero.com/RADStudio/Seattle/en/Declaring_Types for details). Can someone explain, because of what reason the error ignores my opinion? (o;
B. Complete history and more complex details:
First of all: The error:
[dcc32 Fehler] gboDHL.pas(165): E2010 Inkompatible Typen: 'Origin' und 'CountryType'
(which means "incompatible types" in english),
occurs in the following line of code:
AShipmentOrder.Shipment.Shipper.Address.Origin:= DHL_geschaeftskundenversand_api_2.Origin.Create;
Now here is the background:
I try to communicate with the "new" DHL Geschäftskundenversand API v2.2 which still uses SOAP to create a shipment order. DHL_geschaeftskundenversand_api_2 is the completely generated unit from the integrated delphi xe7 WSDL generator for that service.
AShipmentOrder is the instance which represents the top level xml node of the request.
The class "Origin" is implemented/generated this way:
type
CountryType = class(TRemotable)
private
Fcountry: country2;
Fcountry_Specified: boolean;
FcountryISOCode: countryISOType;
Fstate: state;
Fstate_Specified: boolean;
Fcountry_: country;
Fcountry__Specified: boolean;
FcountryISOCode_: countryISOType;
Fstate_: state2;
Fstate__Specified: boolean;
procedure Setcountry(Index: Integer; const Acountry2: country2);
function country_Specified(Index: Integer): boolean;
procedure Setstate(Index: Integer; const Astate: state);
function state_Specified(Index: Integer): boolean;
procedure Setcountry_(Index: Integer; const Acountry: country);
function country__Specified(Index: Integer): boolean;
procedure Setstate_(Index: Integer; const Astate2: state2);
function state__Specified(Index: Integer): boolean;
published
property country: country2 Index (IS_OPTN) read Fcountry write Setcountry stored country_Specified;
property countryISOCode: countryISOType read FcountryISOCode write FcountryISOCode;
property state: state Index (IS_OPTN) read Fstate write Setstate stored state_Specified;
property country_: country Index (IS_OPTN) read Fcountry_ write Setcountry_ stored country__Specified;
property countryISOCode_: countryISOType read FcountryISOCode_ write FcountryISOCode_;
property state_: state2 Index (IS_OPTN) read Fstate_ write Setstate_ stored state__Specified;
end;
Origin = type CountryType; { "http://dhl.de/webservice/cisbase"[GblElm] }
The "property" Origin of AShipmentOrder.Shipment.Shipper.Address is implemented/generated this way:
type
NativeAddressType = class(TRemotable)
...
published
...
property Origin: Origin Index (IS_OPTN or IS_REF) read FOrigin write SetOrigin stored Origin_Specified;
...
end;
If it is unclear, let me say, that ´AShipmentOrder.Shipment.Shipper.Address´ is of class NativeAddressType
I'm using Delphi XE7 on Windows 10, 64bit, compiling to 32bit.
The delphi WSDL generator has different problems, which I had to manage before, and it is really not "nice" that in the output the property names are identical to the class names, but that is not changeable, it seems.
The class "Origin" is not the only class in that generated unit which is named as the property it belongs to, but the error only occurs, when such a class is implemented as
type
A = type B
I tried to find a description of that special declaration variant, but that is quite hard, because the keywords are too often used.
On http://www.delphibasics.co.uk/RTL.asp?Name=Type I found the following explanation:
1.type Name = Existing type  
Refers to an existing type, such as string by a new Name.
 
2.type Name = type Existing type  
This has the same effect as above, but ensures that at run time, variables of this type are identified by their new type name, rather than the existing type name.
If I understand that correctly, that means that in my example the class Origin is not identified anymore as CountryType, but as Origin. That should be fine, because the property Origin is declared as to be of class Origin.
While going on to try to understand that problem, I manually renamed The class "Origin" in the generated unit to OriginType (delphi used that postfix "Type" with several other classes in that unit, not clear, why it has not used it with the Origin-Type). After that, I can exclude a name-conflict, because now the error is
[dcc32 Fehler] gboDHL.pas(165): E2010 Inkompatible Typen: 'OriginType' und 'CountryType'
There seems to be no reason because of what that error occurs.
After some hours without some ideas I found this Embarcadero WIKI entry: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Declaring_Types
After reading and thinking about that, I understood, that the "type" in A = type B is not necessary in that case, because there is no need to handle class A in another way than class B, so I removed that manually in the generated unit and now, it works fine.
I also understand, that a special option in the generator was checked to prevent "name conflicts". If this option is not checked, this problem also should be solved (but maybe some others come around (o; ).
Maybe this helps someone to spent more time outside than with a problem like that (o;
But in the end, I do not understand what was the problem here, because all properties were of type "Origin" and later of "OriginType", also this type was created with Origin.Create and later with OriginType.Create. So the "CountryType" was never used, except for the class declaration of "Origin" / "OriginType". In my opinion this should not lead to that problem, but it does.
Can somebody explain that?
Thank you in advance
Kai
You are getting: E2010 Incompatible types: 'Tb' and 'Ta', which might seems odd:
type
Ta = class
end;
Tb = type Ta;
var
b: Tb;
begin
b := Tb.Create; // E2010 Incompatible types: 'Tb' and 'Ta'
end.
Ta and Tb are two distinct types.
From Type Compatibility you can see that none of the conditions listed indicates that the two distinct types are compatible.
Type Compatibility
Every type is compatible with itself. Two distinct types are compatible if they satisfy at least one of the following conditions.
They are both real types.
They are both integer types.
One type is a subrange of the other.
Both types are subranges of the same type.
Both are set types with compatible base types.
Both are packed-string types with the same number of characters.
One is a string type and the other is a string, packed-string, or Char type.
One type is Variant and the other is an integer, real, string, character, or Boolean type.
Both are class, class-reference, or interface types, and one type is derived from the other.
One type is PAnsiChar or PWideChar and the other is a zero-based character array of the form array[0..n] of PAnsiChar or PWideChar.
One type is Pointer (an untyped pointer) and the other is any pointer type.
Both types are (typed) pointers to the same type and the {$T+} compiler directive is in effect.
Both are procedural types with the same result type, the same number of parameters, and type-identity between parameters in corresponding positions.
The reason for the compiler error is thus: When you call Tb.Create, the compiler identifies that with Ta.Create and since b is of a distinct non-compatible type, it is not accepted.
You can call that a flaw, but it follows strict type rules and can easily be corrected like shown below.
Declaring Tb = class(Ta) would resolve the compiler error since Tb is derived from Ta.
Also declaring Tb = Ta would resolve the compiler error, since they would denote the same type (not two distinct types) and thus assignment compatible.

In Delphi, how do I make a type passable only between functions/methods of a class [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 7 years ago.
Improve this question
I currently have the following code;
interface
{...}
type
TMyRecord = record
List : TShellList;
Tree : TShellTree;
Image : TImage;
end;
TSDIAppForm = class(TForm)
{ Published declarations }
private
function GetChildren(Sheet : TTabSheet) : TMyRecord;
public
{ Public declarations }
end;
As I understand it this means that TMyRecord is a global type visible to the whole program. The type only needs to be visible within the class, although objects of the type do need to be passed and returned as parameters to between "private" functions/procedures of the class. How can I do that? I can't declare the type under the "private" part of the class interface, and if I declare it in the implements then I don't believe it is visible to be used in the interface function prototypes. Also, I think implements/interface relate more to visibility within the unit than the class. Do I need to declare theGetChildren() function in some other way?
As noted in other answers, in versions of Delphi that support nested types you can simply declare the type within the required scope and visibility.
For older versions of Delphi you can achieve a similar outcome by using an untyped var parameter to avoid having to reference the 'private type' in the interface section of your unit:
TSDIAppForm = class(TForm)
..
procedure GetChildren(Sheet : TTabSheet; var aRecord);
..
end;
For convenience and declarative type enforcement in the implementation of the method you can use an absolute declaration to create a local variable to act as placeholder for the untyped parameter:
procedure TSDIAppForm.GetChildren( Sheet : TTabSheet;
var aRecord);
var
result: TMyRecord absolute aRecord;
begin
result.List := ...;
// etc
end;
In this case, since the function has no direct return value and uses the var param in a directly analagous way you might choose to use the name result, as illustrated. Or of course you can use any other name for the local variable you prefer.
In use, you would simply call this method as normal with an appropriate variable in the var param:
var
myRec: TMyRecord;
begin
..
sdiForm.GetChildren(someSheet, myRec);
..
end;
In this way, you can keep a type which is an implementation detail truly confined to the implementation section of your unit.
NOTE: This technique can also be useful in situations where typed var parameters might otherwise cause the compiler to complain about 'formal var parameter types not matching'.
You should of course always consider carefully whether they are the right approach. Not least because whenever you use untyped parameters of course you take on a greater responsibility for ensuring type safety in your code. The potential for abuse should be obvious, but they sometimes offer advantages as well (as in this case, removing entirely a type from the interface section that is arguably most properly entirely confined to the implementation section).
They can also be a useful tool to keep in mind if you create code that you might wish to make available to users of older versions of Delphi where private types etc are not available.
Per Uwe, just declare in private section. Tested in XE8. The following works
TSDIAppForm = class(TForm)
private
type
TMyRecord = record
List : TShellList;
Tree : TShellTree;
Image : TImage;
end;
function GetChildren(Sheet : TTabSheet) : TMyRecord;
public
{ Public declarations }
end;

Delphi: Memory leak when creating a TStringList inside a class

I have this pieces of code
TSql = class
private
FConnString: TStringList;
public
property ConnString: TStringList read FConnString write FConnString;
constructor Create;
destructor Destroy;
end;
var
Sql: TSql;
...
implementation
{$R *.dfm}
constructor TSql.Create;
begin
//inherited Create;
FConnString:=TStringList.Create;
end;
destructor TSql.Destroy;
begin
FConnString.Free;
//inherited Destroy;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Sql.Create;
Sql.ConnString.Add('something');
showmessage(Sql.ConnString.Text);
Sql.Destroy;
end;
Why when creating the FConnString is creating a memory leak, after a press the button?
..................................
..................................
..................................
..................................
There are two things I see. The first of which was already covered by the other comments and answer regarding the lack of "override" on the destructor.
The second issue is the property declaration itself. In general, you should never declare a property that references an object field in the "write" clause. The reason is that assigning to that property will "leak" the existing instance in that field. Use a method for the "write" clause of the property declaration:
property ConnString: TStringList read FConnString write SetConnString;
...
procedure TSql.SetConnString(Value: TStringList);
begin
FConnString.Assign(Value);
end;
Also notice that this method does not overwrite the FConnString field either. It merely copies the "value" or the "content" of the Value TStringList into the FConnString instance. In this manner the TSql instance is in complete and total control over the lifetime of that field. It is the responsibility of the code assigning that property to control the lifetime of the Value TStringlist.
EDIT The actual problem
The original code in the question was as follows:
procedure TForm1.Button1Click(Sender: TObject);
begin
Sql.Create;
Sql.ConnString.Add('something');
showmessage(Sql.ConnString.Text);
Sql.Destroy;
end;
The problem line was Sql.Create; which should have been Sql := TSql.Create;. The reason this causes a memory leak is as follows:
Sql.Create; is called from a nil reference.
This calls TStringList.Create; and attempts to assigned the result to FConnString.
Because Sql is a nil reference, this triggers an Access Violation.
The problem is there is no way to destroy the TStringList instance that was created.
Additional problems in original answer
Your destructor is virtual, you're not overriding.
You're not calling your inherited Destructor.
TSql = class
private
FConnString: TStringList;
public
property ConnString: TStringList read FConnString write FConnString;
constructor Create;
destructor Destroy; override; //Correction #1
end;
destructor TSql.Destroy;
begin
FConnString.Free;
inherited Destroy; //Correction #2
end;
EDIT
A few general tips:
I applaud you for using composition (making FConnString a member instead of inheriting from TStringList). However, by exposing it publicly, you would lose many of the benefits. Specifically you would be exposed to Law of Demeter violations. I'm not saying never do it. But be aware that you can create a maintenance problem down the line if a large amount of client code accesses ConnString directly.
Declaring FConnString: TStringList; violates the principle of Program to an interface, not an implementation. TStringList is a specific implementation of TStrings and this declaration prevents use of other subclasses of TStrings. This is more of a problem combined with #1: if in a few years time you find and want to switch to a different/better subclass implementation of TStrings client code that is binding to TStringList now creates much more work and risk. Basically the preferred aproach can be summed up as:
Declare the variable as the abtract base type.
Create the instance as the specifically chosen implementation subclass.
Let polymorphism ensure subclass behaviour is applied correctly in overridden methods.
Again this is a general guideline. If you specifically need access to properties/methods that were added at the TStringList level of the hierarchy, then you have to bind to that class. But if you don't need it, don't do it.
You forgot to declare Destroy() with the override specifier, so TSql.Destroy is not actually being called when the object is destroyed.
destructor Destroy; override;
The object is not created correctly. It must be:
Sql:=TSql.Create;
From here is coming the memory leak.