¿Constructor genérico con restricción de parámetro?

TMyBaseClass=class
  constructor(test:integer);
end;

TMyClass=class(TMyBaseClass);

TClass1<T: TMyBaseClass,constructor>=class()
  public
    FItem: T;
    procedure Test;
end;

procedure TClass1<T>.Test;
begin
  FItem:= T.Create;
end;

var u: TClass1<TMyClass>;
begin
  u:=TClass1<TMyClass>.Create();
  u.Test;
end;

¿Cómo hago para crear la clase con el parámetro entero? ¿Cuál es la solución?

preguntado el 27 de agosto de 11 a las 16:08

5 Respuestas

Simplemente encasilla a la clase correcta:

type
  TMyBaseClassClass = class of TMyBaseClass;

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

También es probable que sea una buena idea hacer que el constructor sea virtual.

Respondido 06 Oct 12, 19:10

Esta es la mejor manera que he visto de resolver el problema. Curiosamente, miré en mi propio código, intenté cambiar a este enfoque y me di cuenta de que a veces se toman decisiones en tiempo de ejecución sobre qué clase crear, lo que significa que el enfoque descrito en mi respuesta tiene un uso. Por lo tanto, dejé esa respuesta allí en caso de que sea útil para alguien. - David Heffernan

@David ¡Gracias! ¿Crees que el encasillado final de tu edición? T(...) ¿es necesario? No veo por qué; la instancia devuelta tiene que ser y será de tipo T, por definición. ¿Se me escapa algo? ¡Gracias! - Ondrej Kelle

Es necesario. TMyBaseClassClass(T).Create devuelve una nueva instancia de TMyBaseClass, pero necesita regresar T que es una clase más especializada. - David Heffernan

También creo que tu respuesta sería mucho más fuerte sin la alternativa. El primer bloque de código simplemente lo clava por completo. Pero bueno, esa es solo mi opinión. - David Heffernan

Pregunta: ¿Por qué hacer que el constructor sea virtual? Solo quiero saber por qué. Tu primera solución es correcta. Y sí, la cosa T (...) es necesaria. Intenté hacer su primera solución sin él y obtuve los errores en la respuesta de Herffernan. Así que gracias y por favor cuéntenos sobre el constructor virtual. - netboy

Podría considerar darle a la clase base un método explícito para la inicialización en lugar de usar el 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;

Por supuesto, esto solo funciona si la clase base y todas las subclases están bajo su control.

Respondido 27 ago 11, 22:08

+1 esta es una buena solución hasta que desee poder crear instancias de T fuera de la clase genérica. - David Heffernan

@David uno podría ofrecer al constructor con el parámetro entero además del Initialize método. - jpfollenius

Eso aún dejaría a los clientes la oportunidad de llamar al constructor sin parámetros y omitir llamar a Initialize, lo que sería un error. Por supuesto, podrías defenderte de eso interno de la clase, pero sería complicado. Si adopta el enfoque que sugiere (y como sugerí en un momento casi idéntico), preferiría que el constructor sea privado. Por lo que puedo decir, no hay buena solución a este problema y es bastante deprimente si es así. - David Heffernan

Noticias

La solución ofrecida por @TOndrej es muy superior a lo que escribí a continuación, aparte de una situación. Si necesita tomar decisiones en tiempo de ejecución sobre qué clase crear, el siguiente enfoque parece ser la solución óptima.


He actualizado mi memoria de mi propia base de código que también se ocupa de este problema exacto. Mi conclusión es que lo que estás intentando lograr es imposible. Estaría encantado de que se demuestre que estoy equivocado si alguien quiere estar a la altura del desafío.

Mi solución es que la clase genérica contenga un campo FClass que es de tipo class of TMyBaseClass. Entonces puedo llamar a mi constructor virtual con FClass.Create(...). Yo pruebo eso FClass.InheritsFrom(T) en una afirmación. Todo es deprimentemente no genérico. Como dije, si alguien puede probar que mi creencia es incorrecta, votaré, eliminaré y me regocijaré.

En su configuración, la solución alternativa podría verse así:

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;

Otra solución más elegante, si es posible, es utilizar un constructor sin parámetros y pasar la información adicional en un método virtual de T, tal vez llamado Initialize.

Respondido el 22 de Septiembre de 13 a las 17:09

Sí, lo sé, no es posible en C # también. Pero estoy buscando una solución aquí. - netboy

[Error DCC]: E2010 Tipos incompatibles: 'TMyClass' y 'TMyBaseClass' - netboy

Bien, creo que puedo adivinar de qué se trata ese error. Necesitas enviar el resultado del constructor a T. Ver mi código. - David Heffernan

No puedo decir qué estás haciendo mal. No puedo ver tu código. Mi código, dado arriba, funciona. Oh, acabo de corregir un par de pequeños errores tipográficos, pero tú también los habrías arreglado. - David Heffernan

+1 por creatividad. ¿Te sientes apocalíptico por esto (o la bestia no tiene nada que ver con el apocalipsis)? - Marjan Venema

Lo que parece para trabajar en Delphi XE, es llamar a T.Create primero, y luego llamar al método Create específico de la clase como un método después. Esto es similar a la respuesta de Rudy Velthuis (eliminada), aunque no presento un constructor sobrecargado. Este método también parece funcionar correctamente si T es de TControl o clases como esa, por lo que podría construir controles visuales de esta manera.

No puedo probar en 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;

Respondido el 08 de enero de 13 a las 02:01

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);

Respondido 24 Abr '15, 12:04

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.