jueves, 22 de mayo de 2014

Tipos Genéricos en C# - Parte 4: Clases Genéricas

Tabla de Contenido

0. Introducción
1. ¿Qué es una Clase Genérica?
1.1 Declaración de una clase genérica
1.2 Ejemplo de una clase genérica
1.3 Clase genérica con múltiples parámetros de tipo
2. Casos de herencia
2.1 Herencia concreta
2.2 Herencia cerrada
2.3 Herencia abierta
2.4 Herencia cerrada de clases no-genéricas a partir de clases genéricas
2.5 Herencia de clases genéricas con múltiples parámetros
2.6 Herencia con restricciones
3. Consideraciones de Diseño Clases Genéricas
4. Conclusiones
5. Glosario
6. Literatura & Enlaces

0. Introducción

En esta cuarta parte de la serie de artículos de tipos genéricos en C# nos vamos a enfocar en el concepto de clases genéricas. Descubriremos el porqué las clases genéricas constituyen la pieza fundamental para la construcción de las colecciones integradas en la biblioteca de clases del Framework .NET. Otro tema a tratar, será el de guía de diseño de clases genéricas; además veremos la herencia entre tipos genéricos y no-genéricos, las limitaciones y los cuidados que debemos tomar en cuenta a la hora de realizar la construcción de una jerarquía de herencia.

1. ¿Qué es una Clase Genérica?

El modelamiento de una clase genérica compromete la aplicación o seguimiento de un principio básico, el de la generalización. Bien, hemos comprendido que las colecciones son un ejemplo fehaciente de esta aplicación. Dicho lo anterior, podemos definir a una clase genérica como un tipo de dato que posee operaciones generalizadas o independientes del tipo de dato a manipular.

1.1 Declaración de una clase genérica

Para declarar una lista genérica, utilizamos la siguiente sintaxis:

{modificador de acceso} class {nombre de la clase} <T [,T...]> {restricciones}
{
// Cuerpo de declaración
}

Descripción puntual:
  • {modificador de acceso}: podemos usar cualquier de los modificadores de acceso disponibles en C# -cfr. Modificadores de Acceso en C#-.
  • {nombre de la clase}: El nombre de la clase debe seguir las convenciones y reglas de declaración de identificadores.
  • <T [,T...]>: Conjunto de parámetros (argumentos) de tipo (cfr-. Parámetros de Tipo).
  • {restricciones}: Las restricciones impuestas a los parámetros de tipo (ver sección Restricciones en parámetros de tipo en Parámetros de Tipo).
Un ejemplo particular de declaración:

internal class ListaGenerica<T>

O también:

public class TipoGenerico<T> where T : class

Con esta última se especifica que el parámetro de tipo T está restringido a un tipo de dato concreto o una interfaz.

1.2 Ejemplo de una clase genérica

Creemos la estructura de datos (colección) Pila [5] (Stack, en inglés) para almacenar o recuperar cualquier tipo de dato.

Archivo Pila.cs:

En la línea 18 se declara la clase genérica Pila (siguiendo la regla sintáctica de la sección anterior): internal class Pila<T>. (No hemos especificado ninguna restricción para el parámetro de tipo T). En la línea 24 creamos un arreglo estándar con elementos de datos de tipo T (aquí quiero repetir lo que dije en el artículo anterior: en tiempo de ejecución, la CLR se encarga de substituir cada ocurrencia de T por los tipos concretos.) El constructor (líneas 27-30) inicializa en 0 el marcador de posición del arreglo datos. Los metodos Push (líneas 33-36), y Pop (líneas 39-42) se encargan de agregar y retirar datos del arreglo datos, respectivamente.

Creemos código cliente para probar nuestra estructura Pila:

Pila<double> puntosFlotantes = new Pila<double>();

puntosFlotantes.Push(0.577215664901D);
puntosFlotantes.Push(1.618033988D);
puntosFlotantes.Push(1.414213562D);

double raizDos = puntosFlotantes.Pop(); // 1.414213562
double mediaAureal = puntosFlotantes.Pop(); // 1.618033988
double constanteEuler = puntosFlotantes.Pop(); // 0.577215664901

La instancia de Pila que creamos -puntosFlotantes- almacena instancias del tipo por valor double. En las líneas subsiguientes se agregan tres constantes matemáticas a la pila, más adelante, se extrae esos valores siguiendo el modo de extracción de esta estructura (último-en-entrar-primero-en-salir (LIFO [6])).

> Prueba de ejecución.

1.3 Clase genérica con múltiples parámetros de tipo

Una clase genérica puede contener uno o más parámetros de tipo. La sintaxis declarativa sigue la notación de la sección 1.1. Un ejemplo abstracto:

public class ClaseGenerica<T, U, V>
{

}

A los parámetros (argumentos) de tipo también se les puede asignar restricciones:

public class ClaseGenerica<T, U, V> where T : class
{

}

En la biblioteca de clases del Framework .NET encontramos la colección Dictionary<T> [7] que sigue esta especificación. Esta es su firma:
Firma de la colección Dictionary (N:System.Collections.Generic)
Figura 1. Firma de la colección Dictionary (N:System.Collections.Generic) [7].
(Esta firma posee características avanzadas que no discutiré aquí.)

2. Casos de Herencia

La operación de herencia en las clases genéricas tiene casos especiales a raíz de las restricciones sobre los parámetros de tipos, que si no prestamos cuidado, se puede cometer varios errores o problemas de compilación o ejecución. Estudiémoslos:

2.1 Herencia concreta

Con herencia concreta me refiero al proceso definir un tipo de dato con miembros de uso específico y funcional. Una clase genérica, implícitamente, hereda de la clase superior Object. Sin embargo, podemos explicitar la herencia desde una clase abstracta o clase concreta. En código fuente C#, lo anterior se alcanza de esta manera:

class ClaseBase { }

class SubclaseGenerica<T> : ClaseBase { }

El hecho de que la clase SubclaseGenerica sea genérica, no implica que su superclase también lo deba ser. (Object tampoco es una clase genérica, evidentemente.) Además, SubclaseGenerica es una clase concreta que puede definir operaciones y campos para ofrecer funcionalidad específica a otros tipos.

2.2 Herencia cerrada

Una clase genérica puede heredar de otra que también posea esta misma naturaleza, sin embargo, en la operación de herencia podemos establecer una restricción para el parámetro por tipo (en tiempo de ejecución).

Podemos comprender lo anterior mejor, si utilizamos código fuente C#:

class ClaseBase <T> { }

class SubclaseGenerica<T> : ClaseBase <long> { }

En tiempo de ejecución, podemos hacer algo esto:

ClaseBase<long> sg = new SubclaseGenerica<string>();

Gracias a Jon Skeet [8] por la aclaración de este concepto en [9].

2.3 Herencia abierta

Este es el tipo de herencia opuesta a la anterior: podemos definir una clase genérica que herede de una clase genérica sin la restricción sobre el parámetro de tipo.

class ClaseBase <T> { }

class SubclaseGenerica<T> : ClaseBase <T> { }

2.4 Herencia cerrada de clases no-genéricas a partir de clases genéricas

Podemos hacer que una clase no-genérica herede a partir de una clase genérica, siempre cuando esta última especifique un tipo concreto para el parámetro de tipo. Así:

class ClaseBase <T> { }

class SubclaseNoGenerica : ClaseBase <int> { }

En caso contrario, si hubiéramos hecho esto:

class SubclaseNoGenerica : ClaseBase { }

El compilador generaría el error CS0246 [10].

2.5 Herencia de clases genéricas con múltiples parámetros de tipo

Para heredar de una clase genérica que posea múltiples parámetros, es necesario que la subclase genérica posea al menos dos parámetros de tipo, o que la superclase especifique un tipo de dato concreto como argumento en la definición de herencia. Así:

class ClaseBase <T, U> { }

class SubclaseGenerica<T> : ClaseBase <T, int> { }

class SubclaseGenerica<T, U> : ClaseBase <T, U> { }

2.6 Herencia con restricciones

Una superclase genérica que posea parámetros o argumentos de tipo con restricciones, en la subclase se debe explicitar tal restricción:

class ElementoNodo<T> where T : System.IComparable<T>, new () { }

class ElementoEspacialNodo<T> : ElementoNodo<T> where T : System.IComparable<T>, new() { }

3. Consideraciones de Diseño de Clases Genéricas

El diseño de una clase persigue un principio de diseño: el de la generalización. Para alcanzarlo, la clase genérica debe poseer un grado de abstracción suficiente que, por ejemplo, permita manipular tipos de una jerarquía de herencia que pertenezca a un modelo de un problema concreto.

Aquí, desde [2], dejo un listado de consideraciones a tomar en cuenta al diseñar clases genéricas:
  • Contrastar los tipos de datos a a generalizar y que quepan en la categoría de parámetros de tipo de la clase genérica.
  • Establecer las restricciones de los parámetros de tipos.
  • Definir una jerarquía de herencia de tipos genéricos consistente.
  • Usar interfaces que permitan la comparación de instancias concretas, por ejemplo, IComparable<T>.

4. Conclusiones

Dedicamos más tiempo en comprender el porqué y la utilidad de las clases genéricas en C#: definición, declaración, ejemplificación. Estudiamos cuidadosamente hasta 6 casos especiales de herencia de genéricos. La comprensión de estos casos evita que caigamos en problemas técnicos con el compilador. Al final se presentaron algunas consideraciones esenciales a la hora de diseñar clases genéricas en C#.

5. Glosario

  • Argumento de tipo
  • Clase
  • Clase genérica
  • Clase no-genérica
  • Elemento
  • Herencia
  • Nodo
  • Parámetro de tipo

6. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Generic Classes (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/sz6zd40f.aspx
[3]: Modificadores de Acceso en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/04/modificadores-de-acceso-en-c.html
[4]: Genéricos en C# - Parte 3: Parámetros de Tipo | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/tipos-genericos-en-c-parte-3-parametros.html
[5]: Stack (abstract data type) - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Stack_(abstract_data_type)
[6]: LIFO (computing), the free encyclopedia - https://en.wikipedia.org/wiki/LIFO_%28computing%29
[7]: Dictionary(TKey, TValue) Class (System.Collections.Generic) - http://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx
[8]: User Jon Skeet - Stack Overflow - http://stackoverflow.com/users/22656/jon-skeet
[9]: c# - What is the purpose of a closed constructed type? - Stack Overflow - http://stackoverflow.com/questions/23814790/what-is-the-purpose-of-a-closed-constructed-type
[10]: Compiler Error CS0246 - http://msdn.microsoft.com/en-us/library/w7xf6dxs.aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.