Delphi-PRAXiS
Seite 1 von 2  1 2   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Generics: Instanz-Erzeugung in generischer Klasse (https://www.delphipraxis.net/163659-generics-instanz-erzeugung-generischer-klasse.html)

Sir Rufo 9. Okt 2011 21:03


Generics: Instanz-Erzeugung in generischer Klasse
 
Ich habe hier eine generische Klassen/Interface Definition, die von TComponent abgeleitet ist.

Wie bekomme ich die Klasse jetzt dazu eine passende Instanz zu erzeugen?
Delphi-Quellcode:
TGenClass<T:Class, Constructor> = class
sagt ja nur aus, dass die Klasse T einen parameterlosen Constructor hat.
TComponent hat aber einen Constructor mit Parameter (obwohl ich dem ledigleich ein schnödes nil mitgeben würde).
Das
Delphi-Quellcode:
T.Create;
findet der Compiler gut, aber es ist nicht so wie ich das möchte, denn
Delphi-Quellcode:
var
  MyButton : IGenericLink<TButton>;
begin
  MyButton := TGenericLink<TButton>.Create;
  MyButton.Link.OnClick := ButtonClick; // hier rummst es, und das soll es nicht
end;
und
Delphi-Quellcode:
T.Create(nil);
findet der Compiler total blöde :(

Sinn und Zweck dieser Übung ist das Binding von Komponenten einer Form zu einer Controller-Unit.
Dabei soll es die Controller-Unit aushalten, wenn auf der Form nicht alle Komponenten vorhanden sind. (Darum hält TGenericLink eben ein Dummy-Object vor, auf das zugegriffen wird, wenn kein gültiger Link gebildet werden konnte).

Wer kann mir da mal auf den lahmen Gaul helfen?

Die ganze Unit mal zum überfliegen:
Delphi-Quellcode:
unit FormBinding;

interface

uses
  Classes;

type
  IGenericLink<T: TComponent, Constructor> = interface
    ['{7650F483-5FDD-431F-96D1-65536B127BB5}']
    function GetLink: T;
    procedure SetLink(const Value: T);
    property Link: T read GetLink write SetLink;
    procedure LinkTo(const Value: TComponent); overload;
    procedure LinkTo(const Owner: TComponent;
      const ComponentName: string); overload;
    function IsLinked: Boolean;
    function GetOnLinkChanged: TNotifyEvent;
    procedure SetOnLinkChanged(const Value: TNotifyEvent);
    property OnLinkChanged: TNotifyEvent read GetOnLinkChanged
      write SetOnLinkChanged;
  end;

  TGenericLink<T: TComponent, Constructor> = class(TInterfacedObject,
    IGenericLink<T>)
  private
    fInt: T;
    fExt: T;
    fOnLinkChanged: TNotifyEvent;
    function GetLink: T;
    procedure SetLink(const Value: T);
    function GetOnLinkChanged: TNotifyEvent;
    procedure SetOnLinkChanged(const Value: TNotifyEvent);
  public
    constructor Create;
    destructor Destroy; override;
    property Link: T read GetLink write SetLink;
    procedure LinkTo(const Value: TComponent); overload;
    procedure LinkTo(const Owner: TComponent;
      const ComponentName: string); overload;
    function IsLinked: Boolean;
    property OnLinkChanged: TNotifyEvent read GetOnLinkChanged
      write SetOnLinkChanged;
  end;

implementation

{ TGenericLink<T> }

constructor TGenericLink<T>.Create;
begin
  inherited Create;
  fInt := T.Create; // <<-- ???
end;

destructor TGenericLink<T>.Destroy;
begin
  fInt.Free;
  inherited;
end;

function TGenericLink<T>.GetLink: T;
begin
  if Assigned(fExt) then
    Result := fExt
  else
    Result := fInt;
end;

function TGenericLink<T>.GetOnLinkChanged: TNotifyEvent;
begin
  Result := fOnLinkChanged;
end;

function TGenericLink<T>.IsLinked: Boolean;
begin
  Result := Assigned(fExt);
end;

procedure TGenericLink<T>.LinkTo(const Owner: TComponent;
  const ComponentName: string);
begin
  LinkTo(Owner.FindComponent(ComponentName));
end;

procedure TGenericLink<T>.LinkTo(const Value: TComponent);
begin
  if Assigned(Value) and Value.InheritsFrom(T) then
    SetLink(Value)
  else
    SetLink(nil);
end;

procedure TGenericLink<T>.SetLink(const Value: T);
begin
  fExt := Value;
  if Assigned(OnLinkChanged) then
    OnLinkChanged(Self);
end;

procedure TGenericLink<T>.SetOnLinkChanged(const Value: TNotifyEvent);
begin
  fOnLinkChanged := Value;
end;

end.

webcss 9. Okt 2011 21:22

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Wie ist's hiermit

Delphi-Quellcode:
constructor TGenericLink<T>.Create;
begin
  inherited Create;
  fInt := T(TComponent.Create(nil)); // <<-- ???
end;
oder

Delphi-Quellcode:
constructor TGenericLink<T>.Create;
begin
  inherited Create;
  fInt := T(TComponent(T).Create(nil)); // <<-- ???
end;
Ich meine, ich hätte das so schonmal probiert und es hat geklappt... aber keine Gewähr, ist schon spät :wink:

daywalker9 9. Okt 2011 21:30

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Wenn mans etwas dynamischer haben will:

Delphi-Quellcode:
constructor TTestClass<T>.create;
var
  f   : T;
  Info : PTypeInfo;
begin
  Info := TypeInfo(T);
  f := T(GetTypeData(Info)^.ClassType.Create);
end;

stahli 9. Okt 2011 21:55

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Hmm, ich hätte auch gedacht, dass T in dem Fall TButton entspricht und entsprechend T.Create(nil) möglich sein müsste.
Ist das dann als Bug bzw. fehlendes Feature anzusehen oder geht das Problem vom Konzept her völlig in Ordnung?

himitsu 9. Okt 2011 22:36

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Also, du mußt auf jeden Fall "irgendwie" das virtuelle
Delphi-Quellcode:
Create(...)
der TComponents aufrufen.

Das ist eben ein krankes Problem der Generics ... du hast zwar gesagt, es sollen nur TComponent und Nachfolger sein, aber scheinbar bieten dir die Generigs nur TObject und Nachfolger an, beim Zugriff auf Methoden, also nur das standardmäßige Create des TObjekts (ohne Vererbung).

Ich würde auch zu
Delphi-Quellcode:
TComponentClass(T).Create(nil)
tendieren. (wenn ich nicht ständig Probleme bei Typecasts hätte)


Das
Delphi-Quellcode:
T(GetTypeData(Info)^.ClassType.Create);
geht jedenfalls genauso wenig, da es ebenso wieder nur das Standard-TObject-Create aufruft.
Wenn, dann muß man sich über die RTTI schon das richtige Create besorgen.

Wäre ja schön gewesen, wenn man in TObject das Create virtuell gemacht hätte, dann könnte man dort das Standard-Create auf das entsprechende Create der entsprechenden Komponentenklasse umleiten können. (bei TComponent hätte man
Delphi-Quellcode:
Create
auf
Delphi-Quellcode:
Create(nil)
weiterleiten können, wärend bei "ältesten"
Delphi-Quellcode:
Create(nil)
dann auf
Delphi-Quellcode:
inherited Create
geleitet)

Sir Rufo 10. Okt 2011 01:09

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Jo, merci erst mal ... obschon das alles nicht zum Ziel geführt hat ... mich dünkt wohl auch warum ...

Zitat:

Zitat von stahli (Beitrag 1129518)
Hmm, ich hätte auch gedacht, dass T in dem Fall TButton entspricht und entsprechend T.Create(nil) möglich sein müsste.
Ist das dann als Bug bzw. fehlendes Feature anzusehen oder geht das Problem vom Konzept her völlig in Ordnung?

Ich denke mal nicht, denn in der generischen Klasse ist TButton nicht bekannt sondern nur abstrakt als T (irgendwas ab TComponent).
Nachfahren von TComponent können aber auch den Constructor überschreiben:
Delphi-Quellcode:
TMyComponent = class(TComponent)
public
  constructor Create; reintroduce;
end;
und schon gibt es für diese Ableitung kein
Delphi-Quellcode:
Create(AOwner:TComponent)
mehr.

Womöglich ist das so nicht anders machbar, aber da ich auch keine Lust habe für jedwede Ableitung von TComponent ein Interface zu implementieren, behelfe ich mir nun so, dass ich einfach eine Dummy-Instanz beim Erzeugen mitgebe.
(Diese Dummy-Instanz wird dann am Schluss von selbiger einfach ins Nirwana geschickt)
Delphi-Quellcode:
var
  MyButton : IGenericLink<TButton>;
begin
  MyButton := TGenericLink<TButton>.Create( TButton.Create( nil ) );
  MyButton.Link.OnClick := ButtonClick; // jetzt rummst auch nix mehr ... logisch
end;
Nicht schön, aber funktioniert :stupid:

r2c2 10. Okt 2011 08:07

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Zitat:

Zitat von Sir Rufo (Beitrag 1129550)
Ich denke mal nicht, denn in der generischen Klasse ist TButton nicht bekannt sondern nur abstrakt als T (irgendwas ab TComponent).

Genau das ist das Problem, ja.

Zitat:

Nachfahren von TComponent können aber auch den Constructor überschreiben:
Delphi-Quellcode:
TMyComponent = class(TComponent)
public
  constructor Create; reintroduce;
end;
und schon gibt es für diese Ableitung kein
Delphi-Quellcode:
Create(AOwner:TComponent)
mehr.
Das stimmt nicht. Du siehst doch schon am reintroduce, dass hier nichts überschrieben, sondern maximal verdeckt wird. Der alte Konstruktor existiert also weiterhin. Zudem unterscheiden sich die Signaturen, was bedeutet, dass das reintroduce unnötig ist, da noch nichtmal verdeckt, sondern nur überladen wird.

Was mich ja eher irritiert, ist dass das der Compiler TButton hier überhaupt zulässt. Ich bin jetzt kein experte in Delphi-Generics, aber
Delphi-Quellcode:
TGenericLink<T: TComponent, Constructor>
bedeutet doch wohl, dass T einen parameterlosen Konstruktor haben muss. Und TButton hat AFAIR keinen. Das sollte der Compiler doch eigentlich bemeckern. Oder wo ist mein Denkfehler?

Im übrigen gibt es mit ziemlicher Sicherheit eine schönere Lösung, als dein Workaround. Vermutlich über RTTI. Sowas in der Art macht Delphi ja bei den Formulardaten, die aus den dfms gelesen werden. Auch da müssen TComponent-Nachfahren dynamisch erzeugt werden, ohne, dass die konkrete Klasse zur Designzeit schon klar wäre.

mfg

Christian

Stevie 10. Okt 2011 08:15

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Zitat:

Zitat von Sir Rufo (Beitrag 1129550)
Ich denke mal nicht, denn in der generischen Klasse ist TButton nicht bekannt sondern nur abstrakt als T (irgendwas ab TComponent).
Nachfahren von TComponent können aber auch den Constructor überschreiben:
Delphi-Quellcode:
TMyComponent = class(TComponent)
public
  constructor Create; reintroduce;
end;
und schon gibt es für diese Ableitung kein
Delphi-Quellcode:
Create(AOwner:TComponent)
mehr.

Das stimmt so nicht ganz. Du kannst immernoch TMyComponent.Create(MyOtherComponent) aufrufen. Zusätzlich sollte man niemals den Konstruktor eines TComponent ohne den Owner überschreiben, denn diesen benutzt auch die IDE, wenn man die Komponente auf dem Form platziert. Auch wenn du keine in der IDE registrierte Komponente baust.

Manchmal muss man, wenn man weiß, dass T von einem bestimmten Typ ist (da man ihn ja als Type constraint angegeben hat) trotzdem noch herumtricksen, da man nicht einfach alle Methoden dieses Typs benutzen kann.

Delphi-Quellcode:
TComponent(T).Create(nil)
geht auf jeden Fall nicht.

Das hier geht:

Delphi-Quellcode:
type
  TGenericLink<T: TComponent> = class
  private
    FLink: T;
    function CreateComponent(AClass: TComponentClass): TComponent;
  public
    constructor Create;
    property Link: T read FLink;
  end;

function TGenericLink<T>.CreateComponent(AClass: TComponentClass): TComponent;
begin
  FLink := T(AClass.Create(nil));
end;

constructor TGenericLink<T>.Create;
begin
  CreateComponent(TComponentClass(T));
end;
Zitat:

Zitat von r2c2 (Beitrag 1129563)
Im übrigen gibt es mit ziemlicher Sicherheit eine schönere Lösung, als dein Workaround. Vermutlich über RTTI. Sowas in der Art macht Delphi ja bei den Formulardaten, die aus den dfms gelesen werden. Auch da müssen TComponent-Nachfahren dynamisch erzeugt werden, ohne, dass die konkrete Klasse zur Designzeit schon klar wäre.

Delphi macht da nix über RTTI (was das Erzeugen des Objects angeht). Das geht nämlich erst mit der neuen RTTI ab 2010. Vorher gingen nur published Methoden und der Constructor ist nicht published. Es wird also genauso über die TComponentClass gemacht.

Sir Rufo 10. Okt 2011 09:46

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
@Stevie
You Made My Day :thumb:

Stevie 10. Okt 2011 09:57

AW: Generics: Instanz-Erzeugung in generischer Klasse
 
Always a pleasure ;)

Mir fiel gerade noch auf, dass es doch mit nem "einfachen" cast ohne den Umweg über die extra Methode geht:
Delphi-Quellcode:
constructor TGenericLink<T>.Create;
begin
  FLink := T(TComponentClass(T).Create(nil));
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:00 Uhr.
Seite 1 von 2  1 2   

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