Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Hace tiempo que los esperbamos ! Finalmente estn aqu: los genricos en Delphi Win32. Estas pequeas maravillas llegan con Delphi 2009. Este tutorial te da opcin a comprenderlos, aprender a utilizarlos y, a continuacin, disear tus propias clases genricas. Vuestros comentarios, crticas, sugerencias, etc. sern bienvenidos en el blog.
I. Introduccin
I-A. Requisitos
Este tutorial no es para el principiante en Delphi que nunca ha practicado la programacin orientada a objetos. Requiere conocer el lenguaje de Delphi antes, y un importante conocimiento de la POO. En cuanto a los genricos, no es necesario el conocimiento previo para la correcta comprensin y asimilacin de este tutorial. Sin embargo, dado el nmero de artculos sobre los genricos existentes en otros idiomas, no cubrir en detalles las preguntas como "Cmo usar los genricos correctamente?". La siguiente seccin introduce rpidamente al lector en el concepto de los genricos, pero si usted no sabe an lo que es, la mejor comprensin se har siguiendo este tutorial a travs de ejemplos. Aqu hay algunos artculos que tratan los genricos en otros lenguajes / plataformas. Los conceptos son siempre los mismo, y gran parte de su puesta en escena tambin.
Les gnriques sous Delphi .NET por Laurent Dardenne ; Generics avec Java Tiger 1.5.0 por Lionel Roux ; Cours de C/C++ - Les template por Christian Casteyde.
Pg. 1 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Puede observar directamente lo extraordinario del tipo T. Pero, es esto realmente un tipo? No, es un parmetro genrico. Con esta clase, si necesito una lista de nmeros enteros, utilizo TList<Integer>, en lugar de un "simple" TList con muchos operadores necesarios en el cdigo! Los genricos nos permiten, por tanto, de alguna manera, declarar una serie de clases (potenciales) de una sola vez, o ms exactamente declarar un modelo de clase, pero con uno (o varios) tipos como parmetro, que se pueden cambiar a voluntad. A cada nueva instancia con un nuevo tipo real, es como si estuviera escribiendo una nueva clase real, desde el modelo y, por tanto, otorgarle un verdadero potencial a estas clases. Como ya he dicho, no voy a detenerme en el por-qu de la forma genrica. Voy a entablar de inmediato su uso sobre una utilidad diaria. Si todava no entienden, no importa: todo se ir aclarando gradualmente poco a poco. Tambin puede consultar el tutorial Les gnriques sous Delphi .NET de Laurent Dardenne.
Pg. 2 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
program TutoGeneriques; {$APPTYPE CONSOLE} uses SysUtils, Classes, Generics.Collections; procedure WriteSquares(Max: Integer); var List: TList<Integer>; I: Integer; begin List := TList<Integer>.Create; try for I := 0 to Max do List.Add(I*I); for I := 0 to List.Count-1 do WriteLn(Format('%d*%0:d = %d', [I, List[I]])); finally List.Free; end; end; var Max: Integer; begin try WriteLn('Introduzca un nmero entero :'); ReadLn(Max); WriteSquares(Max); except on E:Exception do Writeln(E.Classname, ': ', E.Message); end; ReadLn; end.
Qu diferencia aprecia usted en este cdigo? En primer lugar, por supuesto, la declaracin de la variable List y la creacin de la instancia. Al nombre del tipo TList, hemos adjuntado en un parmetro real (o su equivalente: es un tipo, no un valor) entre los signos < y >. (En Ingls, se les llama angle brackets, lo que significa literalmente: parntesis en forma de ngulos.) Usted podr apreciar pues, que para utilizar una clase genrica, es necesario, asignarle un nombre e indicar un tipo de parmetro. Por ahora, vamos a utilizar aqu siempre un tipo real. Algunos idiomas permiten un genrico, lo que se conoce el tipo de inferencia, que es para que el compilador adivine el tipo (que se llama parmetro de tipo). Este no es el caso Delphi para Win32. Esa es la razn por la que no se puede escribir:
Pg. 3 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
ste segundo asunto es ms importante y menos visible, ya que se trata de una ausencia. De hecho, no es necesaria la conversin de enteros en punteros, y viceversa! Prctico, no? Y, sobre todo, mucho ms legible. Adems, la seguridad del tipo est mejor gestionada. Con las conversiones, siempre se corre el riesgo de equivocarse, y aadir un puntero a una lista destinada a contener enteros, o viceversa. Si utiliza correctamente los genricos, usted probablemente casi nunca necesitar las conversiones y, por tanto, cometer menos errores. Adems, si intenta aadir un tipo Pointer a una clase de objeto TList<Integer>, es el compilador quien le mande de paseo. Antes de los genricos, un error de este estilo podra no haber sido revelado ms que por un valor aberrante, despus de su explotacin! Tenga en cuenta que he elegido el tipo Integer para limitar el cdigo que no est directamente vinculado a la nocin de genricos, pero podramos haber utilizado cualquier tipo en su lugar.
Como el comentario seala, esto no es porque un TComponent puede ser asignados a una TObject que un TList<TComponent> pueda ser asignados a un TList<TObject>. Para entender por qu, piense que TList<TObject>.Add (Value: TObject), permitira, si la asignacin fuera validada, insertar un tipo de valor TObject en una lista de TComponent! El otro punto importante a precisar es que TList<T> no es en modo alguno especializacin de Tlist, o una generalizacin de la misma. De hecho, son dos tipos totalmente diferentes, el primero declarado en la unidad Generics.Collections, y el segundo en la unidad Classes! Veremos ms adelante en este tutorial, es posible hacer ciertas cesiones a travs de las restricciones de los parmetros genricos.
Pg. 4 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Pg. 5 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Destaque el paso a heredar de TpointComparer, este hereda TComparer<TPoint>. Vea pues que es posible heredar una clase "simple" de una clase genrica, a condicin de que se le proporcione un valor real para el parmetro genrico. Para utilizar nuestro comparador, basta con crear una instancia y pasarla a la lista del constructor. Aqu est este pequeo programa que crea 10 puntos al azar, los ordena y muestra la lista ordenada.
function DistanceToCenter(const Point: TPoint): Extended; inline; begin Result := Sqrt(DistanceToCenterSquare(Point)); end; procedure SortPointsWithTPointComparer; const MaxX = 100; MaxY = 100; PointCount = 10; var
Pg. 6 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Y dnde est la liberacin de la instancia al comparador en todo esto? Simplemente en el hecho que TList<T> toma como comparador una interfaz del tipo TComparer<T>. As es cmo la ordenacin de referencia se aplica (implementada en TComparer<T>). Por lo tanto, no hay necesidad de preocuparse por la vida, ni la liberacin del comparador. Si usted no tiene ni idea del funcionamiento de las interfaces en Delphi, le ser muy beneficioso poder consultar el tutorial Les interfaces d'objet sous Delphi de Laurent Dardenne.
Pg. 7 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Pg. 8 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
La nica diferencia, por supuesto, es, por lo tanto, la creacin del comparador. Mientras que el resto de la utilizacin de la lista es idntico (afortunadamente!). Internamente, la clase Construct crea una instancia de TDelegatedComparer<T>, que toma como parmetro de su constructor la referencia de rutina que se ocupar de la comparacin. La llamada a Construct devuelve pues un objeto de este tipo, al amparo de la interfaz IComparer<T>. Bueno, finalmente resulta muy simple. De hecho, debemos reconocer esto: los genricos estn aqu para facilitarnos la vida ! Pero como aad antes, se puede asignar una rutina annima a una referencia de rutina. Vamos a ver como resulta:
procedure SortPointsWithAnonymous; var List: TList<TPoint>; // ... begin List := TList<TPoint>.Create(TComparer<TPoint>.Construct( function(const Left, Right: TPoint): Integer begin Result := DistanceToCenterSquare(Left) - DistanceToCenterSquare(Right); end)); // Todo igual a continuacin... end;
Esta forma de creacin es especialmente interesante si este es el nico lugar donde usted necesita la rutina de comparacin. Hablando de rutinas annimas: s, estas pueden acceder a las variables locales de la rutina / mtodo inclusivo. Y s, ella puede hacerlo an despus del regreso de esta rutina / mtodo inclusivo. El siguiente ejemplo muestra cmo:
function MakeComparer(Reverse: Boolean = False): TComparison<TPoint>; begin Result := function(const Left, Right: TPoint): Integer begin Result := DistanceToCenterSquare(Left) - DistanceToCenterSquare(Right); if Reverse then Result := -Result; end; end; procedure SortPointsWithAnonymous; var List: TList<TPoint>; // ... begin List := TList<TPoint>.Create(TComparer<TPoint>.Construct( MakeComparer(True))); //siempre lo mismo a continiacin
Pg. 9 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Interesante, verdad? Llegamos al final de esta pequea vuelta por los comparados utilizados con TList<T>, y con ella la introduccin a los genricos a travs del uso de esta clase. En el prximo captulo, vamos a empezar a ver cmo podemos escribir nuestra propia clase genrica.
Una rama del rbol se etiquetar con un valor de tipo T, y tendr de 0 a ms hijos. Cuando se destruye una rama, o nodo, este liberan a todos sus hijos. Cmo declaramos un valor del tipo T, pues es simple, se declara FValue: T; de la misma forma que cualquier otro tipo normal. Para mantener la lista de los hijos, utilizaremos un TObjectList<U>, declarado en Generics.Collections. He utilizado deliberadamente U aqu, porque no debemos confundirnos. De hecho, U ser un TTreeNode<T> ! Pues s, se puede utilizar como parmetro de tipo genrico real a una clase genrica. No implementamos aqu un rbol de bsqueda o de clasificacin. Por lo tanto, no necesitamos un comparador como con TList<T>. En cuanto a los campos, quedar de este modo:
type TTreeNode<T> = class(TObject) private FParent: TTreeNode<T>; FChildren: TObjectList<TTreeNode<T>>; FValue: T; end;
Extrao? Si lo piensas bien, no tanto. Se ha dicho antes que un tipo genrico poda ser sustituido por cualquier tipo. Entonces, por qu no una clase genrica?
Pg. 10 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
function GetIsRoot: Boolean; inline; function GetIsLeaf: Boolean; inline; function GetDepth: Integer; protected procedure AncestorChanged; virtual;
Pg. 11 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
property IsRoot: Boolean read GetIsRoot; property IsLeaf: Boolean read GetIsLeaf; property Value: T read FValue write SetValue; end;
Identifiquemos lo que an es importante aqu. De hecho, no mucho. Si no es que a cada vez que un parmetro es del tipo T, se declara como const. En efecto, no sabemos de antemano lo que podr contener T. Y hay opciones de sea un tipo "pesado" (como un string o un record), por lo que es ms eficiente utilizar const. Ya que const nunca esta penalizado (no tiene ningn efecto sobre los tipos "ligeros" como Integer), se utiliza siempre que trabajemos con tipos genricos. El concepto de tipo pesado o ligero es un concepto que me es propio, y nada oficial, por eso utilizo las comillas. Entiendo por tipo pesado un tipo cuyos parmetros ganan (en la velocidad de ejecucin) al ser pasados como const siempre que sea posible. Se trata, en general, de tipos que abarcan ms de 4 octetos (excluyendo flotantes y Int64), y los que requieren inicializacin. Como (muy) gruesos, retengamos las cadenas, los record, las matrices, interfaces (no clases) y los Variant. Sin embargo, cuando un parmetro es del tipo TTreeNode<T>, no aadimos const. De hecho, lo que ser T, TTreeNode<T> quedar como un tipo de clase, que es un tipo ligero.
La nico importante a considerar aqu es que, a cada aplicacin de un mtodo de una clase genrica, debe especificarse de nuevo la <T> junto al del nombre de la clase. En efecto, esto es necesario ya que TTreeNode podra perfectamente existir en otros lugares, y, por tanto, confundirse con otro identificador.
En el caso de la inicializacin de un campo objeto, no es estrictamente necesario. Ya que la inicializacin de un objeto arranca de por si todos sus campos a los valores predeterminados por defecto. Pero no he encontrado un lugar mejor para que presentarle esta pseudo-rutina.
Para llamar a este call-back, escribimos exactamente lo mismo que con un tipo procedimiento, la misma forma que una llamada de rutina, pero con la variable del tipo referencia de rutina en lugar del nombre de la rutina . Para implementar la segunda versin, utilizamos la primera, transmitiendo en el parmetro Action... Una rutina annima!
{* Recorrido prefijado sobre los nodos del rbol @param Action Accin a realizar sobre cada valor *} procedure TTreeNode<T>.PreOrderWalk(const Action: TValueCallBack<T>); begin PreOrderWalk( procedure(const Node: TTreeNode<T>) begin Action(Node.Value); end); end;
Cuanto menos, es elegante como estilo de Programacin, no? Van a decir ustedes que me repito con mis const, pero bueno: el parmetro Node de la rutina annima fue declarado como const cuando es claramente del tipo clase. Es que debe ser compatible todava con la matriz del tipo TValueCallBack<TTreeNode<T>>, que solicita que su parmetro sea const ;-).
Pg. 14 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
III-E. Y el resto
El resto, pues bueno ... Es clsico ;-) Nada nuevo. No me voy a eternizar sobre l, pero la fuente completa est disponible para su descarga, al igual que todas las dems, al final del tutorial.
Pg. 15 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Para obtener ms informacin acerca de la redefinicin de los operadores, ver el tutorial Sobrecarga de operadores en de Delphi 2006 para Win32 de Laurent Dardenne. Es pues un tipo inmutable (No se puede modificador ms el estado una vez que se haya creado). La implementacin de tres operadores de conversin es bastante simple. El segundo de ellos (uno con un parmetro de tipo Pointer) est aqu para permitir la asignacin : = nil.
uses
Pg. 16 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
De lo que resulta :
5 nil 10
Lo han entendido todo? Es genial, porque no he dado ninguna explicacin. Es la prueba fiel de que son fciles, los genricos :-). El cdigo fuente completo de Generics.Nullable est en el zip de fuentes de este tutorial, se puede descargar al final del tutorial.
Pg. 17 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Un tipo de clase descendiente de una clase determinada; Un tipo interfaz descendiente de una interfaz dada, o un tipo clase que implemente esta interfaz; Tipo ordinal, coma-flotante o record; Un tipo de clase que tenga un constructor sin argumento.
Lo que impone, respectivamente, que T deber sustituirse por la clase TStream o uno de sus descendientes; por IInterfaceList o por uno de sus descendientes, por un tipo ordinal, flotante o record (un tipo de valor no-nulo, en la terminologa de Delphi ), o finalmente por una clase que tenga un constructor pblico sin argumentos.
<T: Class> debe utilizarse en lugar de <T: TObject> ... Se entiende bien que class sea aceptado, pero uno se pregunta por qu se rechaza Tobject? Es posible combinar varias interfaces restringidas, o una clase restringida y una o varias interfaces restringidas. En este caso, el tipo real utilizado debe satisfacer simultneamente todas las restricciones. La restriccin constructor tambin puede combinarse con la restriccin de clase y / o de interfaz. Incluso es posible combinar un record con uno o varios de interfaz, pero no veo cmo un tipo puede satisfacer ambas al mismo tiempo (en .NET es posible, pero por el momento, no en Win32 !) Se podra, pero esto no es ms que pura especulacin por mi parte, que sea en previsin de una versin futura, en la que el tipo Integer, por ejemplo, "implementara" la interfaz IComparable<Integer>. Lo que dara pues un tipo que una cumplira las restricciones de una declaracin como <T: registro, IComparable<T>>.
Pg. 18 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Pg. 19 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Una vez ms, si elimina aqu la restriccin constructor, el compilador marcar un error sobre el T.Create.
Pg. 20 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
As, la clase TDictionary<TKey,TValue> toma como parmetros dos tipos. El primero es el tipo de las llaves, el segundo el tipo de los elementos. Esta clase implementa una tabla hash. No se equivoque: TKey y TValue son ambos los parmetros genricos (formales), no dos tipos reales. No deje engaar por la nomenclatura del cdigo. La sintaxis de la declaracin es un poco laxa sobre este punto. Es posible separar los tipos con comas (,) o por punto y coma (;), eventualmente mezclando los dos cuando hay ms de dos tipos. Tanto al nivel de la declaracin de la clase, como en el nivel de la implementacin de los mtodos. En contra, en el uso de un tipo genrico, debe utilizar siempre comas ! Sin embargo, si coloca una o ms instrucciones sobre un tipo que no es el ltimo en la lista, debe utilizar un punto-y-coma para separarla de la prxima. De hecho, una coma indica que habr una segunda restriccin. Asimismo, permtanme proponerle una norma de estilo - que no es la seguida por Embarcadero. Utilice siempre un punto-y-coma en la declaracin del tipo genrico (all dnde sea muy probable aadir instrucciones) y usar comas en todas las dems ocasiones (implementacin de mtodos, y utilizacin del tipo). Como no tengo mejor ejemplo de tipo genrico para ofrecerle que el mencionado TDictionary<TKey,TValue>, le aconsejo que consulte el cdigo de esta clase (definido en la unidad Generics.Collections, probablemente tendr sus propias dudas ). Vea tan slo una muestra:
type TPair<TKey,TValue> = record Key: TKey; Value: TValue; end; // Hash table using linear probing TDictionary<TKey,TValue> = class(TEnumerable<TPair<TKey,TValue>>) // ... public // ... procedure Add(const Key: TKey; const Value: TValue); procedure Remove(const Key: TKey); procedure Clear; property Items[const Key: TKey]: TValue read GetItem write SetItem; default; property Count: Integer read FCount; // ... end;
Como habra visto, TDictionary<TKey,TValue> utiliza nombre de tipos genricos ms explcitos que la T que hemos utilizado hasta ahora. Usted debera hacer lo mismo, siempre que el tipo tenga un significado especial, como es aqu el caso. Y, sobre todo, cuando haya ms de un tipo en el parmetro.
Pg. 21 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Pues no! No es posible declarar una rutina global con parmetros genricos. Una posible razn sera el paralelismo con la sintaxis de Delphi.NET para reducir los costes de desarrollo y mantenimiento, internamente. Para remediar esta falta, se utilizan pues los mtodos de clase. Y an mejor, se precisan como esttico. No hay mucho que ver con la palabra-clave del mismo nombre en C++. Se trata aqu de enlace esttico. Esto quiere decir que este mtodo, no dispone de Self, y que su llamada tiene mtodos de clases virtuales, o tiene constructores virtuales, no est "virtualizado". En otras palabras, como si no fuera virtual. Al final, esto hace del mtodo de la clase esttico una genuina rutina global, pero con un espacio de nombre diferente. A diferencia de algunos lenguajes, no es necesario que al menos un parmetro retome cada tipo genrico introducido en el mtodo. Por lo tanto, es posible escribir un mtodo fantasma Dummy<T>(Int: Integer): Integer, que no tiene ningn parmetro formal y cuyo tipo es el parmetro genrico T. En C++, por ejemplo, esto no sucedera.
Pg. 22 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Nota del traductor: los signos menor < y mayor >, definidos como corchetes angulares (segn el termino en ingls) en algn tratado de HTML, no recibe una terminologa propia en HTML. El autor utiliza el termino francs Chevrons que en mi Larousee se entiende como galn (refirindose a los signos de rango militar), aunque podramos interpretar tambin como corchetes. Personalmente he preferido el termino escuadras ya que dan ms la imagen de contenedor, ms real con el uso asignado. Me sorprende que en HTML no reciban una nominacin ms especfica.
Pg. 23 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Ntese la llamada Min<T>: Es indispensable especificar en la llamada tambin (o los) el tipo real utilizado. Esto contrasta con otros lenguajes como C++. Ahora queremos proponer una cuarta versin sobrecargada, siempre con un nico parmetro Items, pero con un parmetro T sin restricciones. Esta versin debe utilizar TComparer<T>.Default. Pero esto no es posible ! Porque, a pesar que las restricciones sobre los tipos cambian, los parmetros (argumentos) son los mismos. As que ambas versiones son completamente ambiguas! As pues, la siguiente declaracin adicional no soporta la compilacin :
type TArrayUtils = class public class function Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; overload; static; class function Min<T>(const Items: array of T; const Comparison: TComparison<T>): T; overload; static; class function Min<T: IComparable<T>>( const Items: array of T): T; overload; static; class function Min<T>( const Items: array of T): T; overload; static; // Erreur de compilation end;
Debemos entonces elegir: abandonar el uno u el otro, o utilizar otro nombre. Y como, hasta que los tipos bsicos como Integer soporten la interfaz IComparable<T>, corre el riesgo de utilizar tanto el uno primero como el segundo, ser preciso pues optar por otro nombre ;-)
type TArrayUtils = class public class function Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; overload; static; class function Min<T>(const Items: array of T; const Comparison: TComparison<T>): T; overload; static; class function Min<T: IComparable<T>>( const Items: array of T): T; overload; static; class function MinDefault<T>( const Items: array of T): T; static; end; class function TArrayUtils.MinDefault<T>(const Items: array of T): T; begin Result := Min<T>(Items, IComparer<T>(TComparer<T>.Default)); end;
Por qu la conversin explicita en IComparer<T> de Default que es claramente ya un IComparer<T> ? Debido a las referencias de rutina y sobrecargas todava no estn muy bien ensamblados, y el compilador parece lavarse las manos. Sin las conversiones, no soporta la compilacin...
Pg. 24 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Entonces, como lamentablemente parece imposible escribir una class helper para una clase genrica, vamos pues a escribir un mtodo de clase FindAll<T> que realizar esta tarea. Estando privados de class helper, vamos a intentar al menos ser ms generales y trabajar sobre un numerador cualquiera, con una sobrecarga para un numerable cualquiera.
type TListEx = class public class procedure FindAll<T>(Source: const Predicate: TPredicate<T>); class procedure FindAll<T>(Source: const Predicate: TPredicate<T>); end; implementation class procedure TListEx.FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>; const Predicate: TPredicate<T>); begin while Source.MoveNext do begin if Predicate(Source.Current) then Dest.Add(Source.Current); end; end; class procedure TListEx.FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>; const Predicate: TPredicate<T>); begin FindAll<T>(Source.GetEnumerator, Dest, Predicate); end; end.
TEnumerator<T>; Dest: TList<T>; overload; static; TEnumerable<T>; Dest: TList<T>; overload; static;
Pg. 25 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Una vez ms, la conversin es necesaria debido a la sobrecarga. Es bastante triste, pero es as. Si prefiere no utilizar conversiones, utilice nombres diferentes, o deslguese de una de las dos versiones. No depende ms que de usted el completar esta clase con otros mtodos como este :-)
Pg. 26 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Se utiliza de una forma un tanto particular, en el sentido de que el verdadero parmetro de la rutina PrintType se transmite como un tipo parmetrizado.
begin TRTTI.PrintType<Integer>; TRTTI.PrintType<TObject>; TRTTI.PrintType<Pointer>; end; Lo que resulta : Integer tkInteger TObject tkClass Este tipo no dispone de RTTI
Pg. 27 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
Lo que resulta:
TComparison<System.Integer> TTreeNode<System.Integer> tkInterface tkClass
Esto devuelve por el nombre incluido, entre escuadras*, el nombre completamente clasificado de tipo real reemplazando al tipo genrico. Puede resaltar que obtenemos tkInterface por el tipo de referencia de rutina TComparison<T>, lo que demuestra que se trata de una interfaz. Por lo tanto, estos son los nicos cambios aportados a la RTTI con el advenimiento de los genricos. No hablo, por supuesto, de otros cambios aportados en esta versin pero que conciernen a las cadenas Unicode.
X. Conclusin
As concluye el descubrimiento de los tipos genricos con Delphi 2009. Si nunca ha tenido experiencia con los genricos en otro lenguaje, esto puede parecer confuso. Pero son realmente muy prcticos, y cambian las vida de los desarrollador, desde que empieza a jugar con ellos.
XII. Agradecimientos
Un gran agradecimiento a Laurent Dardenne y a SpiceGuid por sus numerosos comentarios y correcciones, que han permitido aumentar la calidad de este tutorial.
Pg. 28 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/
XII. Agradecimientos................................................................................................................................. 28
Pg. 29 de 29
Copyright 2008 - Sbastien Doeraene traduccin por Juan Badell. No est permitida la reproduccin, incluso parcial, que puedan hacer de esta pgina web y todo su contenido: textos, documentos, imgenes, etc, sin el permiso expreso del autor. De lo contrario incurre la ley hasta 3 aos de prisin y hasta 300.000 por daos y perjuicios. http://sjrd.developpez.com/delphi/tutoriel/generiques/