delphi - ¿Cómo implementar IEnumerable<T>?
ienumerable c# (3)
¿Cómo implemento IEnumerable<T>
?
Fondo
Digamos que tengo una clase que quiero implementar IEnumerable<T>
:
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
public
{ IEnumerable<T> }
function GetEnumerator: IEnumerator<T>;
end;
var
IEnumerable<TMouse> mices = TStackoverflow<TMouse>.Create;
Tendría una implementación:
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
public
{ IEnumerable<T> }
function GetEnumerator: IEnumerator<T>;
end;
function TStackoverflow<T>.GetEnumerator: IEnumerator<T>;
begin
Result := {snip, not important here};
end;
Ahora, para ser un buen programador, elegiré que mi clase también admita la interfaz IEnumerable
:
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
public
{ IEnumerable<T> }
function GetEnumerator: IEnumerator<T>;
{ IEnumerable }
function GetEnumerator: IEnumerator;
end;
function TStackoverflow<T>.GetEnumerator: IEnumerator<T>;
begin
Result := {snip, not important here};
end;
function TStackoverflow.GetEnumerator: IEnumerator;
begin
Result := {snip, not important here};
end;
Aquellos de ustedes que saben a dónde va esto, sabrán que implementar IEnumerable
es una IEnumerable
falsa; y hubo que hacerlo de cualquier manera.
Ahora viene el problema
Ese código no se compila, porque:
function GetEnumerator: IEnumerator<T>;
function GetEnumerator: IEnumerator;
Tengo dos métodos con la misma firma (no realmente la misma firma, pero lo suficiente como para que Delphi no pueda distinguirlos):
E2254 El procedimiento sobrecargado ''GetEnumerator'' debe marcarse con la directiva ''sobrecarga''
Ok, bien, los marcaré como overloads
:
function GetEnumerator: IEnumerator<T>; overload;
function GetEnumerator: IEnumerator; overload;
Pero eso no funciona:
E2252 El método ''GetEnumerator'' con parámetros idénticos ya existe
Resolución del método de interfaz
La sobrecarga fue el enfoque incorrecto, deberíamos estar buscando las cláusulas de resolución de métodos :
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
protected
function GetEnumeratorTyped: IEnumerator<T>;
function GetEnumeratorGeneric: IEnumerator;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
end;
{ TStackoverflow }
function TStackoverflow<T>.GetEnumeratorGeneric: IEnumerator;
begin
end;
function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin
end;
Excepto que esto no compila tampoco, por razones que me escapan:
E2291 Falta implementación del método de interfaz IEnumerable.GetEnumerator
Así que olvidemos IEnumerable
Pretenda que no me importa si mi clase no admite IEnumerable
, lo IEnumerable
como una interfaz compatible. En realidad, no cambia nada, ya que IEnumerable<T>
desciende de IEnumerble
:
IEnumerable<T> = interface(IEnumerable)
function GetEnumerator: IEnumerator<T>;
end;
así que el método debe existir y el código que elimina IEnumerable
:
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
protected
function GetEnumeratorTyped: IEnumerator<T>;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
end;
{ TStackoverflow }
function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin
end;
No compila por la misma razón:
E2291 Falta implementación del método de interfaz IEnumerable.GetEnumerator
Muy bien entonces, olvídate de los genéricos.
Así que paremos, colaboremos y escuchemos. IEnumerable ha vuelto como un nuevo invento antiguo:
type
TStackoverflow = class(TInterfacedObject, IEnumerable)
public
function GetEnumerator: IEnumerator;
end;
{ TStackoverflow }
function TStackoverflow.GetEnumerator: IEnumerator;
begin
end;
Excelente, eso funciona. Puedo tener mi soporte de clase IEnumerable
. Ahora quiero soportar IEnumerable<T>
.
Lo cual me lleva a mi pregunta:
Actualización : Olvidé adjuntar el código no funcional completo:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
protected
function GetEnumeratorTyped: IEnumerator<T>;
function GetEnumeratorGeneric: IEnumerator;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
end;
{ TStackoverflow<T> }
function TStackoverflow<T>.GetEnumeratorGeneric: IEnumerator;
begin
end;
function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin
end;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
except
on E: Exception do
Writeln(E.ClassName, '': '', E.Message);
end;
end.
En realidad, es muy simple, aunque no es realmente obvio. La clave es saber que el compilador hace algo de magia con los enumeradores.
Primero, no necesitamos implementar IEnumerator
o IEnumerator<T>
en absoluto. En su lugar, declaramos o poseemos interfaz. Por el bien de este ejemplo, estamos enumerando las Anillas Anchas, por lo que podría verse a continuación. Tenga en cuenta que los nombres de método / propiedad y las firmas son importantes aquí:
IFoobarEnumerator = interface
function GetCurrent: WideString;
function MoveNext: Boolean;
procedure Reset;
property Current: WideString read GetCurrent;
end;
Ahora, la clase que lo rodea recibe un método llamado GetEnumerator
que devuelve algo que parece un enumerador.
Escribí deliberadamente "algo que parece un enumerador ", no "algo que implementa IEnumerator<T>
", porque eso es todo lo que el compilador necesita para permitir que la magia suceda:
function GetEnumerator : IFoobarEnumerator;
Aparte del hecho de que, por supuesto, todavía necesitamos implementar la clase de enumerador para IFoobarEnumerator
, eso es todo. Hemos terminado
Una cláusula de resolución de método puede hacer el trabajo:
type
T<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
public
{ IEnumerable<T> }
function GetEnumeratorGeneric: IEnumerator<T>;
function IEnumerable<T>.GetEnumerator = GetEnumeratorGeneric;
{ IEnumerable }
function GetEnumerator: IEnumerator;
function IEnumerable.GetEnumerator = GetEnumerator;
end;
Parece que tu intento falló, porque ninguno de tus métodos se llamaba GetEnumerator
.En mi código anterior, uno de ellos se llama GetEnumerator
y el otro recibe un nombre diferente.Desde mi experimentación, debe tener exactamente uno de los métodos de implementación que tenga el mismo nombre que el método de interfaz.
Lo que es raro. Yo diría que esto parece un error de compilación. El comportamiento persiste incluso a XE7.
Por otro lado, al menos tienes una manera de avanzar ahora.
Actualizar
De acuerdo, el comentario de Stefan a continuación me lleva, tardíamente, a comprender lo que realmente está sucediendo. Realmente necesitas satisfacer tres métodos de interfaz aquí. Claramente tenemos que proporcionar una implementación para IEnumerable.GetEnumerator
, con el fin de satisfacer IEnumerable
. Pero como IEnumerable<T>
deriva de IEnumerable
, entonces IEnumerable<T>
contiene dos métodos, IEnumerable.GetEnumerator
e IEnumerable<T>.GetEnumerator
. Entonces, lo que realmente quieres poder hacer es algo como esto:
Me resulta un poco difícil hacer un seguimiento de los nombres en conflicto y los genéricos, así que voy a ofrecer un ejemplo alternativo que creo que resalta el punto clave con mayor claridad:
type
IBase = interface
procedure Foo;
end;
IDerived = interface(IBase)
procedure Bar;
end;
TImplementingClass = class(TInterfacedObject, IBase, IDerived)
procedure Proc1;
procedure IBase.Foo = Proc1;
procedure Proc2;
procedure IDerived.Bar = Proc2;
end;
Ahora, tenga en cuenta que el uso de la herencia de interfaz significa que IDerived
contiene dos métodos, Foo
y Bar
.
El código de arriba no se compilará. El compilador dice:
E2291 Falta implementación del método de interfaz IBase.Foo
Lo que, por supuesto, parece extraño porque seguramente la cláusula de resolución del método se ocupó de eso. Bueno, no, la resolución del método se ocupó del Foo
declarado en IBase
, pero no del IDerived
que se heredó.
En el ejemplo anterior podemos resolver el problema con bastante facilidad:
TImplementingClass = class(TInterfacedObject, IBase, IDerived)
procedure Proc1;
procedure IBase.Foo = Proc1;
procedure IDerived.Foo = Proc1;
procedure Proc2;
procedure IDerived.Bar = Proc2;
end;
Esto es suficiente para hacer feliz al compilador. Ahora hemos proporcionado implementaciones para los tres métodos que deben implementarse.
Desafortunadamente, no puede hacer esto con IEnumerable<T>
porque el método heredado tiene el mismo nombre que el nuevo método introducido por IEnumerable<T>
.
Gracias de nuevo a Stefan por llevarme a la iluminación.
Respuesta corta rápida
Hay DOS grupos de interfaces con nombre idéntico, por ejemplo: IEnumerable
no genérico y: IEnumerable<T>
.
Visión general
Tienen el mismo problema.
El problema no es la implementación de la interfaz, o la implementación de la interfaz genérica.
Existe una situación especial con las interfaces genéricas como IEnumerable<T>
, porque también tienen una interfaz IEnumerable
no genérica. Estas interfaces son compatibles con el sistema operativo, no son solo otra interfaz de programación.
Y, también hay varias otras interfaces genéricas y no genéricas, como IEnumerator
o IComparable
que tienen sus propias definiciones de miembros.
Solución
Para resolver el mismo problema, busco las interfaces principales que IEnumerable<T>
y detecto las interfaces no genéricas que coinciden.
Y, clase agregada e intermedia no genérica que implementó las interfaces no genéricas. Esto me permite detectar fácilmente qué interfaces y definiciones de miembros coincidentes faltan.
type
// Important: This intermediate class declaration is NOT generic
// Verify that non generic interface members implemented
TBase = class(TInterfacedObject, IEnumerable)
protected
function GetEnumeratorGeneric: IEnumerator;
public
function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
end;
// Important: This proposed class declaration IS generic,
// yet, it descends from a non generic intermediate class.
// After verify that non generic interface members implemented,
// verify that generic interface members implemented,
T<T> = class(TBase, IEnumerable<T>)
protected
function GetEnumeratorTyped: IEnumerator<T>;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
end;
Y las cosas, son más difíciles, porque hay varios miembros sobrecargados con el mismo identificador, pero diferentes tipos de parámetros o diferentes tipos de retorno, dependiendo de si son de la interfaz genérica o la interfaz no genérica.
La solución de @David Heffernan, donde solo hay una clase, no es mala. Intenté el mismo enfoque, pero fue más que suficiente.
Por lo tanto, apliqué la clase intermedia, porque me permitió averiguar qué interfaces y miembros respectivos, donde faltan.
Aclamaciones.