Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Halb-virtuelle Methoden (https://www.delphipraxis.net/183307-halb-virtuelle-methoden.html)

Der schöne Günther 30. Dez 2014 13:40

Delphi-Version: XE7

Halb-virtuelle Methoden
 
Ich bin zu ungeschickt, in den Weiten des Internets richtig danach zu suchen. Folgender Code:
Delphi-Quellcode:
program Project15; {$APPTYPE CONSOLE} {$R *.res}

uses System.SysUtils;

type
   IMyInterface = interface
      procedure interfaceProc();
   end;

   TMyBase = class(TInterfacedObject, IMyInterface)
      procedure interfaceProc();// virtual;
   end;

   TMyChild = class(TMyBase, IMyInterface)
      procedure interfaceProc();// override;
   end;

procedure TMyBase.interfaceProc();
begin
   WriteLn('TMyBase');
end;

procedure TMyChild.interfaceProc();
begin
   inherited;
   Writeln('TMyChild');
end;

var
   interfaceVariable: IMyInterface;
   classVariable: TMyBase;
begin
   WriteLn('interface variable');
   interfaceVariable := TMyChild.Create();
   interfaceVariable.interfaceProc();

   Write(sLineBreak);

   WriteLn('class type variable');
   classVariable := TMyChild.Create();
   classVariable.interfaceProc();

   readln;
end.
erzeugt die Ausgabe
Code:
interface variable
TMyBase
TMyChild

class type variable
TMyBase
Sprich: Spreche ich die Instanz mit einer Interface-Variable an wird die Methode behandelt, als hätte ich virtual angegeben. Nehme ich die Klassenreferenz wird sie nicht virtual behandelt.

Warum ist das so? Könnte folgendes etwas damit zu tun haben?
http://docwiki.embarcadero.com/RADSt...mplementations


Lange Rede kurzer Sinn: Sollte ich bei implementierten Interface-Methoden nun eine virtual-Direktive dahintersetzen? Eigentlich kann ich mir das ja dann sparen...

alda 30. Dez 2014 13:47

AW: Halb-virtuelle Methoden
 
Ist doch völlig logisch, der Aufruf über die variable "classVariable" hat rein garnichts mit dem Interface zu tun und sollte so auch nicht benutzt werden - also ja, hier bräuchtest Du ein virtual - oder übersehe ich hier etwas ?

Übrigens: der Aufruf von
Delphi-Quellcode:
...
classVariable.interfaceProc();
...
sollte in einer AV resultieren, da die Instanz aufgrund der fehlenden Referenz nach dem Create wieder den ReferenceCount 0 erhält.

Edit:
Jetzt sehe ich worauf Du hinaus willst, hatte die Ausgabe von "TMyBase" UND "TMyChild" beim ansprechen der "interfaceVariable" übersehen. Dieses Verhalten ist mir in der tat noch nicht aufgefallen, aber ich habe auch sehr selten solch eine Konstellation. Spontan würde ich die Klassen in der Tat so deklarieren, wie Du es angedeutet hast:
Delphi-Quellcode:
TMyBase = class(TInterfacedObject, IMyInterface)
   procedure interfaceProc(); virtual;
  end;

TMyChild = class(TMyBase)
   procedure interfaceProc(); override;
end;

Sir Rufo 30. Dez 2014 17:54

AW: Halb-virtuelle Methoden
 
Das sollte schon bekannt sein, dass bei
Delphi-Quellcode:
var
  LInstance : TMyBase;
begin
  LInstance := TMyChild.Create;
  LInstance.interfaceProc; // ruft TMyBase.interfaceProc auf!!!
end;
eben die Methode
Delphi-Quellcode:
TMyBase.interfaceProc
aufgerufen wird, aufgrund der Deklaration von
Delphi-Quellcode:
LInstance : TMyBase
.

Aus diesem Grund muss die Methode zwingend als
Delphi-Quellcode:
virtual
deklariert werden, dann wird die auch in der abgeleiteten und erzeugten Klasse gefunden.

Allerdings funktioniert dies hier
Delphi-Quellcode:
var
  LInstance : TMyChild;
begin
  LInstance := TMyChild.Create;
  LInstance.interfaceProc; // ruft TMyChild.interfaceProc auf!!!
end;
wie erwartet :)

Dejan Vu 30. Dez 2014 18:19

AW: Halb-virtuelle Methoden
 
Also in meinen Augen ist das mal wieder 'typisch halbausgegorener Delphi-Mist'.

Die 2. Deklaration (TMyChild implementiert IMyInterface) ist redundant, da es von TMyClass abgeleitet ist und diese ja schon das Interface implementiert. Weiterhin müsste die 'interfaceproc' Routine als virtual deklariert werden, da sie ja überschrieben wird.

Es muss doch egal sein, ob ich über das Interface oder die Klasse eine Implementierung aufrufe. So, wie das hier von Delphi umgesetzt wurde, ist das doch totaler Murks: Wir erlauben z.B. nur die Verwendung von Interfacevariablen, die Instantiierung erfolgt nur über eine Fabrik. Intern jedoch kann es durchaus sein, das eine Klasse direkt verwendet wird, um z.B. auf public Methoden zugreifen zu können, die nicht im Interface angegeben sind.

Grauslich.


Edit: Der obige Text ist -bis auf den Einwand mit der redundanten Angabe des Interfaces in der Deklaration von TMyChild- Quatsch. Ich lass ihn aber so. Begründung siehe folgende Beiträge

Der schöne Günther 30. Dez 2014 18:38

AW: Halb-virtuelle Methoden
 
Hoppla, stimmt:
Ändere
Delphi-Quellcode:
   TMyChild = class(TMyBase, IMyInterface)
      procedure interfaceProc();// override;
   end;
zu
Delphi-Quellcode:
   TMyChild = class(TMyBase)
      procedure interfaceProc();// override;
   end;
und erhalte plötzlich
Code:
interface variable
TMyBase

class type variable
TMyBase
Total komisch. :roteyes:


Trotzdem macht der Compiler die Methoden irgendiwe "halb-virtuell", denn wenn ich
Delphi-Quellcode:
classVariable
zu TMyChild caste, ruft
Delphi-Quellcode:
interfaceProc()
mittels
Delphi-Quellcode:
inherited
völlig gelassen die Methode der Oberklasse auf. Obwohl nie jemand gesagt hat, dass diese Methode virtuell sein soll.

Sir Rufo 30. Dez 2014 18:46

AW: Halb-virtuelle Methoden
 
Na dann versuch doch mal das hier:
Delphi-Quellcode:
var
  LBase : TMyBase;
  LChild : TMyChild;
begin
  LBase := nil;
  LChild := nil;

  LBase.interfaceProc;
  LChild.interfaceProc;
end;
Aha, jetzt wissen wir das auch ;)

Der schöne Günther 30. Dez 2014 19:04

AW: Halb-virtuelle Methoden
 
:wiejetzt:

Mein Punkt ist: Niemand schreibt irgendwo
Delphi-Quellcode:
virtual
. Trotzdem wird sie "halb virtuell" gemacht: Halb, da ich von der Unterklasse mittels
Delphi-Quellcode:
inherited
die Methode der Oberklasse aufrufen kann obwohl niemand irgendwo
Delphi-Quellcode:
override
sagt.

alda 30. Dez 2014 19:21

AW: Halb-virtuelle Methoden
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1285011)
:wiejetzt:

Mein Punkt ist: Niemand schreibt irgendwo
Delphi-Quellcode:
virtual
. Trotzdem wird sie "halb virtuell" gemacht: Halb, da ich von der Unterklasse mittels
Delphi-Quellcode:
inherited
die Methode der Oberklasse aufrufen kann obwohl niemand irgendwo
Delphi-Quellcode:
override
sagt.

Die Eigenschaft "virtual" sagt ja auch nur aus, dass man die Methode überschreiben darf. Das "inherited" ruft einfach die geerbte Methode auf, das hat ja erstmal nichts mit der Eigenschaft virtual zu tun.

Sir Rufo 30. Dez 2014 20:21

AW: Halb-virtuelle Methoden
 
Du kannst quasi jede Methode (private sind schwierig bis gar nicht zu erreichen) in einer Ableitung überschreiben, aber wenn die Methode nicht als
Delphi-Quellcode:
virtual
deklariert ist, dann bekommst du genau das Verhalten wie du es hier siehst/zeigst, ansonsten ist es so wie erwartet.

Ist eine Methode
Delphi-Quellcode:
virtual
, dann kann man sich mit Delphi-Referenz durchsuchenTVirtualMethodInterceptor in die Methodenaufrufe einklinken, bei allen anderen Methoden ist das nicht möglich. Das liegt an der Verwaltung der Klassenstruktur, die man mit genau diesem
Delphi-Quellcode:
virtual
festlegt.

Sir Rufo 30. Dez 2014 20:25

AW: Halb-virtuelle Methoden
 
Die Dokumentation ist da auch eindeutig

http://docwiki.embarcadero.com/RADSt...ethodenbindung

Dejan Vu 30. Dez 2014 20:35

AW: Halb-virtuelle Methoden
 
Ich muss mich korrigieren, das ist nicht 'typisch halbausgegorener Delphi-Mist', wie ich schrieb, sondern vollkommen korrekt, das das so ist. Verwirrend, aber korrekt.

TMyClass implementiert das Interface und die Methode
Delphi-Quellcode:
interfaceproc
und TMyChild schert sich einen feuchten Kericht darum und ersetzt/überschreibt (wortwörtlich) die Methode durch eine eigene Implementierung.

Dreckig, sollte verboten sein, aber legal.

PS: C# hätte in 'TMyChild' gerne das 'new' Schlüsselwort, damit man sieht, das 'interfaceproc' *neu* implementiert wurde, aber notwendig ist auch dort nicht...

Sir Rufo 30. Dez 2014 22:23

AW: Halb-virtuelle Methoden
 
Ich habe das Beispiel mal erweitert, damit man die ganzen Spielarten sieht
Delphi-Quellcode:
program dp_183307;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils;

type
  IMyInterface = interface
    procedure interfaceProc1( );
    procedure interfaceProc2( );
  end;

  TMyBase = class( TInterfacedObject, IMyInterface )
    procedure interfaceProc1( ); // virtual;
    procedure interfaceProc2( ); virtual;
  end;

  TMyChild = class( TMyBase, IMyInterface )
    procedure interfaceProc1( ); // override;
    procedure interfaceProc2( ); override;
  end;

procedure TMyBase.interfaceProc1( );
begin
  WriteLn( 'TMyBase.interfaceProc1' );
end;

procedure TMyBase.interfaceProc2;
begin
  WriteLn( 'TMyBase.interfaceProc2' );
end;

procedure TMyChild.interfaceProc1( );
begin
  inherited;
  WriteLn( 'TMyChild.interfaceProc1' );
end;

procedure TMyChild.interfaceProc2;
begin
  inherited;
  WriteLn( 'TMyChild.interfaceProc2' );
end;

procedure Test1;
var
  LIntf: IMyInterface;
  LInst: TMyBase;
begin
  WriteLn( 'LInst[TMyBase] := TMyBase.Create' );
  LInst := TMyBase.Create;
  LInst.interfaceProc1;
  LInst.interfaceProc2;
  WriteLn( 'LIntf <- LInst' );
  LIntf := LInst;
  LIntf.interfaceProc1;
  LIntf.interfaceProc2;
end;

procedure Test2;
var
  LIntf: IMyInterface;
  LInst: TMyBase;
begin
  WriteLn( 'LInst[TMyBase] := TMyChild.Create' );
  LInst := TMyChild.Create;
  LInst.interfaceProc1;
  LInst.interfaceProc2;
  WriteLn( 'LIntf <- LInst' );
  LIntf := LInst;
  LIntf.interfaceProc1;
  LIntf.interfaceProc2;
end;

procedure Test3;
var
  LIntf: IMyInterface;
  LInst: TMyChild;
begin
  WriteLn( 'LInst[TMyChild] := TMyChild.Create' );
  LInst := TMyChild.Create;
  LInst.interfaceProc1;
  LInst.interfaceProc2;
  WriteLn( 'LIntf <- LInst' );
  LIntf := LInst;
  LIntf.interfaceProc1;
  LIntf.interfaceProc2;
end;

begin
  try
    WriteLn('Test1');
    Test1;
    WriteLn;
    WriteLn('Test2');
    Test2;
    WriteLn;
    WriteLn('Test3');
    Test3;
  except
    on E: Exception do
      WriteLn( E.ClassName, ': ', E.Message );
  end;
  ReadLn;

end.
Damit erhalten wir folgende Ausgabe
Code:
Test1
LInst[TMyBase] := TMyBase.Create
TMyBase.interfaceProc1
TMyBase.interfaceProc2
LIntf <- LInst
TMyBase.interfaceProc1
TMyBase.interfaceProc2

Test2
LInst[TMyBase] := TMyChild.Create
TMyBase.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2
LIntf <- LInst
TMyBase.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2

Test3
LInst[TMyChild] := TMyChild.Create
TMyBase.interfaceProc1
TMyChild.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2
LIntf <- LInst
TMyBase.interfaceProc1
TMyChild.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2
Jetzt kommt das Schmankerl ... und nehmen bei
Delphi-Quellcode:
TMyChild
das Interface weg
Delphi-Quellcode:
TMyChild = class( TMyBase{, IMyInterface} )
.

Jetzt erhalten wir diese Ausgabe
Code:
Test1
LInst[TMyBase] := TMyBase.Create
TMyBase.interfaceProc1
TMyBase.interfaceProc2
LIntf <- LInst
TMyBase.interfaceProc1
TMyBase.interfaceProc2

Test2
LInst[TMyBase] := TMyChild.Create
TMyBase.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2
LIntf <- LInst
TMyBase.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2

Test3
LInst[TMyChild] := TMyChild.Create
TMyBase.interfaceProc1
TMyChild.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2
LIntf <- LInst
TMyBase.interfaceProc1
TMyBase.interfaceProc2
TMyChild.interfaceProc2
Lustig, gell? ;)


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:51 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz