Author Topic: Off Topic: Problems with properties in delphi  (Read 4937 times)

Marras

  • Posts: 10
I'm sorry for this off-topic post, but the readers here seemes to be gr8 programmers, and most of them delphi-programmers! And I have a somewhat tricky delphi problem that I would be really happy if somone could help me sort out...

I have trouble understanding how inherited and overridden properties in delphi works out.
I want to override a property in order to make its actions different, but it isn't possible. I need to know why, and if there is a solution to my problem.

I have made the following tests:

Code: [Select]
interface

uses Dialogs;

type
TClassA = class
private
  procedure SetStr1(s: string);
public
  procedure SetStr2(s: string); virtual;
  property Str1: String write SetStr1;
  property Str2: String write SetStr2;
end;

TClassB = class(TClassA)
private
  procedure SetStr1(s: string);
public
  procedure SetStr2(s: string); override;
  property Str1: String write SetStr1;
  property Str2: String write SetStr2;
end;

// As you can see, the setter method for Str2 is public and can be overriden
// while the setter method for Str1 is private.

// The property itself for Str1 can be overriden, but not the setter method.

implementation

procedure TClassA.SetStr1(s: string);
begin
ShowMessage('ClassA: '+s);
end;

procedure TClassA.SetStr2(s: string);
begin
ShowMessage('ClassA: '+s);
end;

procedure TClassB.SetStr1(s: string);
begin
ShowMessage('ClassB: '+s);
end;

procedure TClassB.SetStr2(s: string);
begin
ShowMessage('ClassB: '+s);
end;

end.


I have to following code to do my tests:

Code: [Select]

procedure TForm1.Button1Click(Sender: TObject);
var a,aa: TClassA;
  b: TClassB;
begin
a:=TClassA.Create;
b:=TClassB.Create;
aa:=TClassA(TClassB.Create);

b.Str1:='b.Str1';
a.Str1:='a.Str1';
TClassA(b).Str1:='TClassA(b).Str1';
aa.Str1:='aa.Str1';

b.Str2:='b.Str2';
a.Str2:='a.Str2';
TClassA(b).Str2:='TClassA(b).Str2';
aa.Str2:='aa.Str2';

aa.Free;
b.Free;
a.Free;
end;


When i click the button on my form, I get in my ShowMessage dialogs:

Ok with me:
ClassB: b.Str1
ClassA: a.Str1

NOT ok with me:
ClassA: TClassA(b).Str1  
ClassA: aa.Str1

Ok with me:
ClassB: b.Str2
ClassA: a.Str2
ClassB: TClassA(b).Str2
ClassB: aa.Str2

The tests result in this:

I can override a property ONLY if the setter and getter methods are declared as protected or public, but if I override a property with private setter and getter methods, the overriden property will be used if the class is treated as the inherited type, but not if it is treated as the base class.

Isn't this behaviour very dangerous? I don't like it at all. It seemes to me that the great value that I think properties add to the language is lost when this problem arises.

Is there some way to work around this behaviour?

Kind regards, Marcus Johansson

DanaPaul

  • Posts: 335
Re: Off Topic: Problems with properties in delphi
« Reply #1 on: 28 May '03 - 13:14 »
Quote

// The property itself for Str1 can be overriden, but not the setter method.


This is a key point.  Unless the the base class method is virtual or dynamic you are not overriding the method, you are actually hiding it. Only virtual and dynamic methods, functions, procedures, and properties can be truly overriden.  If you enable show "Hints" and "Warnings" in your IDE options you will see a warning when and if ancestor methods are "hidden" instead of the more useful expectation of "overridden."

Quote
I can override a property ONLY if the setter and getter methods are declared as protected or public,


I think a more appropriate definition would state that you can access a property or method only if it is protected or public.  Therefore, it is impossible to override any property or method that you cannot access.

Quote
but if I override a property with private setter and getter methods, the overriden property will be used if the class is treated as the inherited type, but not if it is treated as the base class.


Again, in the example you posted the private method was neither virtual nor dynamic. So the method was never overriden, it was in fact hidden until accessed again while typecast as the original base class.

Quote
Isn't this behaviour very dangerous? I don't like it at all. It seemes to me that the great value that I think properties add to the language is lost when this problem arises.


Yes it is dangerous until we gain a good understanding of how the compiler actually does its "late binding" magic.

Quote
Is there some way to work around this behaviour?


Hide the original property (make it a private method in your inherited class) and give it a different publicname? :)

Or play around with ClassRef :)

« Last Edit: 28 May '03 - 13:16 by DanaPaul »

Marras

  • Posts: 10
Re: Off Topic: Problems with properties in delphi
« Reply #2 on: 2 Jun '03 - 09:33 »
Quote
I think a more appropriate definition would state that you can access a property or method only if it is protected or public.  Therefore, it is impossible to override any property or method that you cannot access.


Yes, but. A property that uses private setter and getter methods can't be overriden, but it can be accessed, because the property itself is public. And a proeprty can't be declared dynamic, it's setter and getter methods can, but have to be declared protected.

So, one way to look at it is that a public property makes private methods visible in some aspects and in some not.

Quote
Yes it is dangerous until we gain a good understanding of how the compiler actually does its "late binding" magic.


True.
I don't like static overriding at all. That's what I think is dangerous.

My problem came when I wanted to override the "Position" and "Size" properties of TStream. Theese properties uses private setter and getter methods. Theese methods uses the protected virtual method "Seek" to perform its actions.

In my case, writing a transparent stream class for WMA decoder in BASS, I didn't want to use that technique. I wanted to override the Position and Size properties.

A class I have written (before I knew about BASS) that handles the playing of sounds takes any stream and treats it's data as RAW PCM data. For each read it checkes the size of the stream and the position to see if it has reached the end of the stream (and by "stream" I mean any TStream descendant).

Whenever I ask the stream for it's size, it is beeing seeked to the very end and the position is returned, and then back again. BASS does not like settings positions in a WMA stream like that all the time, it takes too long. BASS provides me with methods for getting positions and sizes of BASS-streams, so it would be wise to use those.

I have now been forced to do a rather ugly workaround for this to work, and all because properties can't be overriden.

Or maybe I should say it is because the TStream class in delphi is poorly written, considering that properties can't be overriden.

Anyway, thanks for your answer!

DanaPaul

  • Posts: 335
Re: Off Topic: Problems with properties in delphi
« Reply #3 on: 2 Jun '03 - 20:04 »

Quote

I don't like static overriding at all. That's what I think is dangerous.


Actually, static methods can not be overridden. They can be hidden (yuck!) in a class derived from, but not overridden.

Quote
... Theese methods uses the protected virtual method "Seek" to perform its actions ... In my case, writing a transparent stream class for WMA decoder in BASS, I didn't want to use that technique. I wanted to override the Position and Size properties.


You would need to drop down to the abstract class "TStream" itself to override this "Seek" method with appropriate code for a Bass interface...  BASS_ChannelGetPosition, BASS_ChannelSetPosition, etc.

Quote
Or maybe I should say it is because the TStream class in delphi is poorly written, considering that properties can't be overriden.


Well, properties are (in effect) overridden when/if any defined getter and setter methods are virtually or dynamically overridden.  The author of any class, component, or object decides which methods are static, virtual, dynamic, private, protected, or public. And thereby has given their class an abstract usefulness for any given task at hand or limited usefullness for "expected" usage, eh? :)

Marras

  • Posts: 10
Re: Off Topic: Problems with properties in delphi
« Reply #4 on: 3 Jun '03 - 11:55 »
Quote

You would need to drop down to the abstract class "TStream" itself to override this "Seek" method with appropriate code for a Bass interface...  BASS_ChannelGetPosition, BASS_ChannelSetPosition, etc.


How do you mean?

Quote

And thereby has given their class an abstract usefulness for any given task at hand or limited usefullness for "expected" usage, eh? :)


Yes, that's very true.
What makes me irritated is that I think it should not be so hard to see the usefullness of making GetPosition and GetSize virtual... I don't understand why borland has missed out on that...

DanaPaul

  • Posts: 335
Re: Off Topic: Problems with properties in delphi
« Reply #5 on: 3 Jun '03 - 12:39 »
Granted, this is a kludge, but it should work for all situations that recognize that all Streams are not equal. From the top of my head... :)

TBassStream = class(TStream)
 private
   fChannel: THandle;
   fSize: Int64;
   fPosition: Int64;
 public
   function Open(FileName: string);
   function Play: boolean;
   function GetPlayPos: Int64;
   function Read(): Longint; override;
   function Write(): Longint; override;
   function Seek(): Longint; override;
 end;

function TBassStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
 case Origin of
   0: FPosition := Offset;
   1: Inc(FPosition, Offset);
   2: FPosition := FSize - Offset;
 end;
 Result := FPosition;
end;

function TBassStream.Open(FileName: string);
 begin
   fChannel := BASS_CreateStreamFile...
   fSize := BASS_ChannelGetLength...
   end;

function TBassStream.Play;
 begin
   BASS_ChannelSetPosition(fPosition);
   BASS_ChannelPlay();
   end;

function TBassStream.GetPlayPos: Int64;
 begin
   fPosition := BASS_ChannelGetPosition();
   Result := fPosition;
   end;

The Seek Method is passive and Bass controls all access to the stream.  Read and write methods should update fPosition variable accordingly.

For traditional TFileStream access you could add any number of properties and methods that check for access rights, ie: Bass isn't playing the file.
« Last Edit: 3 Jun '03 - 13:19 by DanaPaul »

Marras

  • Posts: 10
Re: Off Topic: Problems with properties in delphi
« Reply #6 on: 10 Jun '03 - 12:14 »
Thats a smart solution, to make the seek method passive...
Thanks for you tip!