delphi generics delphi-xe6

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:

¿Cómo implementar IEnumerable <T>?

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.