Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi generische Liste als Parameter, Vererbung (https://www.delphipraxis.net/185801-generische-liste-als-parameter-vererbung.html)

haentschman 7. Jul 2015 13:23

Delphi-Version: XE

generische Liste als Parameter, Vererbung
 
Hallo alle... :P

Ich habe mehrere generische Listen welche von einer "Basis" Liste abgeleitet sind.
Delphi-Quellcode:
TListBasis<T: class> = class(TObjectList<T>)
public
  constructor Create; virtual;
end;

TList1 = TListBasis<TBlubb>
TList2 = TListBasis<TBla>
Jetzt wollte ich ein Event bauen was bei einer Änderung egal welcher Liste gefeuert wird. Der Empfänger soll dann schauen um welche Liste es sich handelt und entsprechend weiterverarbeiten.
Delphi-Quellcode:
TOnLoadListEvent = procedure (Sender: TObject; aList: TListBasis<T>) of object;

if Assigned(FOnLoadList) then
begin
  FOnLoadList(Self, FList2); // FList2 = Instanz von TList2 
end;
...resultiert in:
Zitat:

[DCC Fehler] dAV3_Preferences.pas(14): E2003 Undeklarierter Bezeichner: 'T'
Wo liegt mein Denkfehler? Geht das überhaupt? Welche Alternativen gibt es?

Danke...

Photoner 7. Jul 2015 13:35

AW: generische Liste als Parameter, Vererbung
 
Delphi-Quellcode:
TListBasis<T: class>
!=
Delphi-Quellcode:
TListBasis<T>
was passiert wenn du das in ersteres änderst?

haentschman 7. Jul 2015 13:40

AW: generische Liste als Parameter, Vererbung
 
Danke für die Rückmeldung. 8-)

Ich habe schon diverse Varianten durch. Diese auch. Ich suche schon den Vormittag nach meinem Denkfehler. Ich denke langsam, das das nicht möglich ist.

Photoner 7. Jul 2015 14:06

AW: generische Liste als Parameter, Vererbung
 
habs:

Delphi-Quellcode:
TOnLoadListEvent<T> = procedure (Sender: TObject; aList: TListBasis<T>) of object;
Einsatzbeispiel:

Delphi-Quellcode:
  private
    FOnLoadList : TOnLoadListEvent<TBla>;
    { Private-Deklarationen }
  public
    property OnLoadList : TOnLoadListEvent<TBla> read FOnLoadList write FOnLoadList;
-----------------------------------------------------------------------------------

Was nicht geht:

Delphi-Quellcode:
  private
    FOnLoadList : TOnLoadListEvent<T>;
    { Private-Deklarationen }
  public
    property OnLoadList : TOnLoadListEvent<T> read FOnLoadList write FOnLoadList;
da gibt es einen Fehler:

[dcc32 Fehler] Unit2.pas(27): E2511 Typparameter 'T' muss ein Klassentyp sein

haentschman 7. Jul 2015 15:06

AW: generische Liste als Parameter, Vererbung
 
Danke für deine Bemühungen... :P

Tja, da kann man dem Event aber nur TBla übergeben. Es sollte aber auch TBlubb gehen. Deshalb hatte ich das über den "Vorfahr" versucht. :(
Alternative wäre für jeden Listentyp ein getrenntes Event. :(

Stevie 7. Jul 2015 15:33

AW: generische Liste als Parameter, Vererbung
 
Klarer Fall von "hier fehlt uns die Covarianz in Delphi"

Der schöne Günther 7. Jul 2015 15:51

AW: generische Liste als Parameter, Vererbung
 
Ich verstehe nicht wo hier Kovarianz ins Spiel kommt. Wir leiten doch hier nirgendwo von
Delphi-Quellcode:
TListBasis<T>
ab. Seine Motivation ist dass seine
Delphi-Quellcode:
TList1
und
Delphi-Quellcode:
TList2
-Instanzen auch ein Event haben, dessen Parameter
Delphi-Quellcode:
TBlubb
bzw.
Delphi-Quellcode:
TBla
-Instanzen sind.

Und das ist doch so gegeben, oder?

Delphi-Quellcode:
   TListBasis<T: class> = class(TObjectList<T>)
      public type
         TOnLoadListEvent = TProc<TObject, TListBasis<T>>;
      public var
         OnLoadList: TOnLoadListEvent;
   end;

   TBlubb = class(TObject);
   TBla = class(TObject);

   TList1 = TListBasis<TBlubb>;
   TList2 = TListBasis<TBla>;

Stevie 7. Jul 2015 15:56

AW: generische Liste als Parameter, Vererbung
 
Die Kovarianz kommt ins Spiel, sobald ich eine TList<derived> als TList<base> behandeln will um ebend nicht für jeden Typen in der Liste einen Speziellen Event Handler zu haben.
Warum? Weil dann jeder, der mal an dieses Event gehen möchte, einen Event handler für genau diesen Typ in der Liste implementieren muss.

Und nebenbei bemerkt und leicht off topic ist es keine gute Idee, ein Event als anonyme Methode zu implementieren.

Allerdings fehlt atm noch die Information, warum der Event handler die Liste braucht. Was wird damit gemacht?

Hier mal hingeschludert, wie über ein Interface (wie in meinem Blogpost erklärt) ein sicherer Lesezugriff auf eine generische Objectliste realisiert werden kann.

Delphi-Quellcode:
program Project1;

{$APPTYPE CONSOLE}

uses
  Generics.Collections,
  SysUtils;

type
  IReadOnlyObjectList = interface
    ['{3DFBDE4F-16A4-4395-AB29-58671BD7EC1E}']
    function GetClassType: TClass;
    function GetCount: Integer;
    function GetItem(Index: Integer): TObject;
    function GetEnumerator: TEnumerator<TObject>;
    property ClassType: TClass read GetClassType;
    property Count: Integer read GetCount;
    property Items[Index: Integer]: TObject read GetItem; default;
  end;

  TListBasis<T: class> = class(TObjectList<T>, IReadOnlyObjectList)
  private
    function GetClassType: TClass;
    function GetCount: Integer;
    function GetItem(Index: Integer): TObject;
    function GetEnumerator: TEnumerator<TObject>;
  protected
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    constructor Create; virtual;
  end;

{ TListBasis<T> }

constructor TListBasis<T>.Create;
begin
  inherited Create;
end;

function TListBasis<T>.GetClassType: TClass;
begin
  Result := T;
end;

function TListBasis<T>.GetCount: Integer;
begin
  Result := inherited Count;
end;

function TListBasis<T>.GetEnumerator: TEnumerator<TObject>;
begin
  Result := TEnumerator<TObject>(inherited GetEnumerator);
end;

function TListBasis<T>.GetItem(Index: Integer): TObject;
begin
  Result := TObject(inherited Items[Index]);
end;

function TListBasis<T>.QueryInterface(const IID: TGUID; out Obj): HRESULT;
begin
  if GetInterface(IID, obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

function TListBasis<T>._AddRef: Integer;
begin
  Result := -1;
end;

function TListBasis<T>._Release: Integer;
begin
  Result := -1;
end;

type
  TOnLoadListEvent = procedure (Sender: TObject; const aList: IReadOnlyObjectList) of object;

  TBla = class
  end;

  TTest = class
  private
    FOnLoadList: TOnLoadListEvent;
    FList: TListBasis<TBla>;
    procedure LoadListHandler(Sender: TObject; const aList: IReadOnlyObjectList);
  public
    constructor Create;
    destructor Destroy; override;
    procedure LoadStuff;
  end;

{ TTest }

constructor TTest.Create;
begin
  inherited Create;
  FList := TListBasis<TBla>.Create;

  FOnLoadList := LoadListHandler;
end;

destructor TTest.Destroy;
begin
  FList.Free;
  inherited;
end;

procedure TTest.LoadListHandler(Sender: TObject; const aList: IReadOnlyObjectList);
var
  obj: TObject;
begin
  Writeln(aList.Count, ' items of type ', aList.ClassType.ClassName);
end;

procedure TTest.LoadStuff;
begin
  FList.AddRange([TBla.Create, TBla.Create, TBla.Create, TBla.Create]);
  if Assigned(FOnLoadList) then
    FOnLoadList(Self, FList);
end;

var
  test: TTest;
begin
  try
    test := TTest.Create;
    try
      test.LoadStuff;
    finally
      test.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

haentschman 7. Jul 2015 16:28

AW: generische Liste als Parameter, Vererbung
 
Hallo alle...
Zitat:

Allerdings fehlt atm noch die Information, warum der Event handler die Liste braucht. Was wird damit gemacht?
Der Hintergrund ist folgender. Der Empfänger des Events, in diesem Falle die Form, stellt den Inhalt der Listen dar. Es sollte ein Eventhandler für alle Listen sein. Im Eventhandler sollte dann über die Listenklasse entschieden werden welches "View" gefüllt wird. Prinpiell ist der Form auch der Lagerplatz der Listen bekannt. Den Inhalt direkt von dort zu beziehen wäre auch nicht das Problem. Ich arbeite aber lieber mit Events, da man dann ggf. das Laden auch in einen Thread auslagern kann und der Thread Bescheid sagt wenn die Daten da sind. Da braucht dann die Anzeigelogik nicht geändert werden.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:08 Uhr.

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