Generics constructor with parameter constraint?

后端 未结 5 1008
死守一世寂寞
死守一世寂寞 2020-12-11 02:54
TMyBaseClass=class
  constructor(test:integer);
end;

TMyClass=class(TMyBaseClass);

TClass1=class()
  public
    FItem: T;
    pr         


        
相关标签:
5条回答
  • 2020-12-11 02:54

    Just typecast to the correct class:

    type
      TMyBaseClassClass = class of TMyBaseClass;
    
    procedure TClass1<T>.Test;
    begin
      FItem:= T(TMyBaseClassClass(T).Create(42));
    end;
    

    Also it's probably a good idea to make the constructor virtual.

    0 讨论(0)
  • 2020-12-11 02:58

    You might consider giving the base class an explicit method for initialization instead of using the constructor:

    TMyBaseClass = class
    public
      procedure Initialize(test : Integer); virtual;
    end;  
    
    TMyClass = class(TMyBaseClass)
    public
      procedure Initialize(test : Integer); override;
    end;
    
    procedure TClass1<T>.Test;
    begin
      FItem:= T.Create;
      T.Initialize(42);
    end;
    

    Of course this only works, if the base class and all subclasses are under your control.

    0 讨论(0)
  • 2020-12-11 03:00
    type
      TBase = class
        constructor Create (aParam: Integer); virtual;
      end;
    
      TBaseClass = class of TBase;
    
      TFabric = class
        class function CreateAsBase (ConcreteClass: TBaseClass; aParam: Integer): TBase;
        class function CreateMyClass<T: TBase>(aParam: Integer): T;
      end;
    
      TSpecial = class(TBase)
      end;
    
      TSuperSpecial = class(TSpecial)
        constructor Create(aParam: Integer); override;
      end;
    
    class function TFabric.CreateAsBase(ConcreteClass: TBaseClass; aParam: Integer): TBase;
    begin
      Result := ConcreteClass.Create (aParam);
    end;
    
    class function TFabric.CreateMyClass<T>(aParam: Integer): T;
    begin
      Result := CreateAsBase (T, aParam) as T;
    end;
    
    // using
    var
      B: TBase;
      S: TSpecial;
      SS: TSuperSpecial;
    begin
      B := TFabric.CreateMyClass <TBase> (1);
      S := TFabric.CreateMyClass <TSpecial> (1);
      SS := TFabric.CreateMyClass <TSuperSpecial> (1);
    
    0 讨论(0)
  • 2020-12-11 03:03

    Update

    The solution offered by @TOndrej is far superior to what I wrote below, apart from one situation. If you need to take runtime decisions as to what class to create, then the approach below appears to be the optimal solution.


    I've refreshed my memory of my own code base which also deals with this exact problem. My conclusion is that what you are attempting to achieve is impossible. I'd be delighted to be proved wrong if anyone wants to rise to the challenge.

    My workaround is for the generic class to contain a field FClass which is of type class of TMyBaseClass. Then I can call my virtual constructor with FClass.Create(...). I test that FClass.InheritsFrom(T) in an assertion. It's all depressingly non-generic. As I said, if anyone can prove my belief wrong I will upvote, delete, and rejoice!

    In your setting the workaround might look like this:

    TMyBaseClass = class
    public
      constructor Create(test:integer); virtual;
    end;
    TMyBaseClassClass = class of TMyBaseClass;
    
    TMyClass = class(TMyBaseClass)
    public
      constructor Create(test:integer); override;
    end;
    
    TClass1<T: TMyBaseClass> = class
    private
      FMemberClass: TMyBaseClassClass;
      FItem: T;
    public
      constructor Create(MemberClass: TMyBaseClassClass); overload;
      constructor Create; overload;
      procedure Test;
    end;
    
    constructor TClass1<T>.Create(MemberClass: TMyBaseClassClass);
    begin
      inherited Create;
      FMemberClass := MemberClass;
      Assert(FMemberClass.InheritsFrom(T));
    end;
    
    constructor TClass1<T>.Create;
    begin
      Create(TMyBaseClassClass(T));
    end;
    
    procedure TClass1<T>.Test;
    begin
      FItem:= T(FMemberClass.Create(666));
    end;
    
    var 
      u: TClass1<TMyClass>;
    begin
      u:=TClass1<TMyClass>.Create(TMyClass);
      u.Test;
    end;
    

    Another more elegant solution, if it is possible, is to use a parameterless constructor and pass in the extra information in a virtual method of T, perhaps called Initialize.

    0 讨论(0)
  • 2020-12-11 03:20

    What seems to work in Delphi XE, is to call T.Create first, and then call the class-specific Create as a method afterwards. This is similar to Rudy Velthuis' (deleted) answer, although I don't introduce an overloaded constructor. This method also seems to work correctly if T is of TControl or classes like that, so you could construct visual controls in this fashion.

    I can't test on Delphi 2010.

    type
      TMyBaseClass = class
        FTest: Integer;
        constructor Create(test: integer);
      end;
    
      TMyClass = class(TMyBaseClass);
    
      TClass1<T: TMyBaseClass, constructor> = class
      public
        FItem: T;
        procedure Test;
      end;
    
    constructor TMyBaseClass.Create(test: integer);
    begin
      FTest := Test;
    end;
    
    procedure TClass1<T>.Test;
    begin
      FItem := T.Create; // Allocation + 'dummy' constructor in TObject
      try
        TMyBaseClass(FItem).Create(42); // Call actual constructor as a method
      except
        // Normally this is done automatically when constructor fails
        FItem.Free;
        raise;
      end;
    end;
    
    
    // Calling:
    var
      o: TClass1<TMyClass>;
    begin
      o := TClass1<TMyClass>.Create();
      o.Test;
      ShowMessageFmt('%d', [o.FItem.FTest]);
    end;
    
    0 讨论(0)
提交回复
热议问题