DE .NET, SQLSERVER Y MÁS, APRENDE CONMIGO!✔

Desarrollo de todo tipo de aplicaciones y Administración de Base de datos con Tecnología Microsoft


UNETE

Genéricos

0

 

Genéricos

Los tipos genéricos se agregaron a la versión 2.0 del lenguaje C# y Common Language Runtime (CLR). Estos tipos agregan el concepto de parámetros de tipo a .NET Framework, lo cual permite diseñar clases y métodos que aplazan la especificación de uno o más tipos hasta que el código de cliente declara y crea una instancia de la clase o del método. Por ejemplo, mediante la utilización de un parámetro de tipo genérico T, se puede escribir una clase única que otro código de cliente puede utilizar sin generar el costo o el riesgo de conversiones en tiempo de ejecución u operaciones de conversión boxing, como se muestra a continuación:

// Declare the generic class.
    public class GenericList<T>
    {
        void Add(T input) { }
    }
    class TestGenericList
    {
        private class ExampleClass { }
        static void Main()
        {
            // Declare a list of type int.
            GenericList<int> list1 = new GenericList<int>();
 
            // Declare a list of type string.
            GenericList<string> list2 = new GenericList<string>();
 
            // Declare a list of type ExampleClass.
            GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
        }
    }

 

Información general acerca de los genéricos

·         Utilice los tipos genéricos para maximizar la reutilización, seguridad de tipos y rendimiento del código.

·         El uso más común de genéricos es crear clases de colección.

·         La biblioteca de clases de .NET Framework contiene varias nuevas clases de colección genéricas en el espacio de nombres System.Collections.Generic. Éstas se deberían utilizar siempre que sea posible en lugar de clases como ArrayList en el espacio de nombres System.Collections.

·         Puede crear sus propias interfaces, clases, métodos, eventos y delegados genéricos.

·         Las clases genéricas se pueden restringir para permitir el acceso a métodos en tipos de datos determinados.

·         Se puede obtener información sobre los tipos que se utilizan en un tipo de datos genérico en tiempo de ejecución y mediante reflexión.

Las clases y los métodos genéricos combinan reusabilidad, seguridad de tipos y eficacia de una manera que sus homólogos no genéricos no pueden. Los tipos genéricos se utilizan frecuentemente con las colecciones y los métodos que funcionan en ellas. La versión 2.0 de la biblioteca de clases de .NET Framework proporciona un nuevo espacio de nombres, System.Collections.Generic, que contiene varias clases nuevas de colección basadas en tipos genéricos. Se recomienda que todas las aplicaciones destinadas a .NET Framework 2.0 y versiones posteriores utilicen las nuevas clases de colección genéricas en lugar de sus homólogas no genéricas anteriores, como ArrayList. Para obtener más información, vea Tipos genéricos en la biblioteca de clases de .NET Framework.

Claro está que también se pueden crear tipos y métodos genéricos personalizados para proporcionar soluciones generalizadas propias y diseñar modelos eficaces que tengan seguridad de tipos. El ejemplo de código siguiente muestra una clase de lista vinculada genérica para propósitos de demostración. (En la mayoría de los casos, es aconsejable utilizar la clase List<T> proporcionada por la biblioteca de clases de .NET Framework en lugar de crear una propia.) El parámetro de tipo T se emplea en diversas ubicaciones donde generalmente se utilizaría un tipo concreto para indicar el tipo del elemento almacenado en la lista. Este parámetro se utiliza de las formas siguientes:

·         Como tipo de un parámetro de método en el método AddHead.

·         Como tipo de valor devuelto del método público GetNext y la propiedad Data en la clase anidada Node.

·         Como tipo de datos de los miembros privados en la clase anidada.

Observe que T está disponible para la clase anidada Node. Cuando se creen instancias de GenericList<T> con un tipo concreto, por ejemplo GenericList<int>, cada aparición de T se reemplazará por int.

// type parameter T in angle brackets
        public class GenericList<T> 
        {
            // The nested class is also generic on T.
            private class Node
            {
                // T used in non-generic constructor.
                public Node(T t)
                {
                    next = null;
                    data = t;
                }
 
                private Node next;
                public Node Next
                {
                    get { return next; }
                    set { next = value; }
                }
                
                // T as private member data type.
                private T data;
 
                // T as return type of property.
                public T Data  
                {
                    get { return data; }
                    set { data = value; }
                }
            }
 
            private Node head;
            
            // constructor
            public GenericList() 
            {
                head = null;
            }
 
            // T as method parameter type:
            public void AddHead(T t) 
            {
                Node n = new Node(t);
                n.Next = head;
                head = n;
            }
 
            public IEnumerator<T> GetEnumerator()
            {
                Node current = head;
 
                while (current != null)
                {
                    yield return current.Data;
                    current = current.Next;
                }
            }
        }

En el ejemplo de código siguiente se muestra cómo el código de cliente utiliza la clase genérica GenericList<T> para crear una lista de enteros. Con sólo cambiar el argumento de tipo, el código siguiente puede modificarse fácilmente para crear listas de cadenas o cualquier otro tipo personalizado:

class TestGenericList
        {
            static void Main()
            {
                // int is the type argument
                GenericList<int> list = new GenericList<int>();
 
                for (int x = 0; x < 10; x++)
                {
                    list.AddHead(x);
                }
 
                foreach (int i in list)
                {
                    System.Console.Write(i + " ");
                }
                System.Console.WriteLine("\nDone");
            }
        }

 

Ventajas de los genéricos

Los tipos genéricos proporcionan la solución a una limitación de las versiones anteriores de Common Language Runtime y del lenguaje C#, en los que se realiza una generalización mediante la conversión de tipos a y desde el tipo base universal Object. Con la creación de una clase genérica, se puede crear una colección que garantiza la seguridad de tipos en tiempo de compilación.

Las limitaciones del uso de clases de colección no genéricas se pueden demostrar escribiendo un breve programa que utilice la clase de colección ArrayList de la biblioteca de clases base de .NET Framework. ArrayList es una clase de colección muy conveniente, que se puede utilizar sin modificar para almacenar tipos de referencia o tipos de valor.

// The .NET Framework 1.1 way to create a list:
            System.Collections.ArrayList list1 = new System.Collections.ArrayList();
            list1.Add(3);
            list1.Add(105);
 
            System.Collections.ArrayList list2 = new System.Collections.ArrayList();
            list2.Add("It is raining in Redmond.");
            list2.Add("It is snowing in the mountains.");

 

Pero esta conveniencia tiene su costo. Cualquier referencia o tipo de valor agregado a un objeto ArrayList se convierte implícitamente a Object. Si los elementos son tipos de valor, se les debe aplicar la conversión boxing cuando se agregan a la lista y la conversión unboxing cuando se recuperan. Tanto las operaciones de conversión de tipos como las de conversiones boxing y unboxing reducen el rendimiento; el efecto de las conversiones boxing y unboxing puede ser muy notable en los casos en los que se deben recorrer en iteración colecciones extensas.

La otra limitación es la ausencia de comprobación de tipos en tiempo de compilación; dado que un objeto ArrayList convierte todo a Object, en tiempo de compilación no hay forma de evitar que el código de cliente haga cosas como la siguiente:

System.Collections.ArrayList list = new System.Collections.ArrayList();
            // Add an integer to the list.
            list.Add(3);
            // Add a string to the list. This will compile, but may cause an error later.
            list.Add("It is raining in Redmond.");
 
            int t = 0;
            // This causes an InvalidCastException to be returned.
            foreach (int x in list)
            {
                t += x;
            }

 

Aunque es perfectamente válido y a veces intencionado si se crea una colección heterogénea, es probable que la combinación de cadenas y valores ints en un objeto ArrayList único sea un error de programación, el cual no se detectará hasta el tiempo de ejecución.

En las versiones 1.0 y 1.1 del lenguaje C#, se podían evitar los riesgos de utilizar código generalizado en las clases de colección de la biblioteca de clases base de .NET Framework escribiendo colecciones propias específicas del tipo. Claro está que, como dicha clase no se puede reutilizar para más de un tipo de datos, se pierden las ventajas de la generalización y se debe volver a escribir la clase para cada uno de los tipos que se van a almacenar.

Lo que ArrayList y otras clases similares realmente necesitan es un modo de que el código de cliente especifique, por instancias, el tipo de datos particular que se va a utilizar. Eso eliminaría la necesidad de convertir a T:System.Object y también haría posible que el compilador realizara la comprobación de tipos. Es decir, ArrayList necesita un parámetro de tipo. Eso es precisamente lo que los tipos genéricos proporcionan. En la colección genérica List<T>, en el espacio de nombres N:System.Collections.Generic, la misma operación de agregar elementos a la colección tiene la apariencia siguiente:

 

// The .NET Framework 2.0 way to create a list
            List<int> list1 = new List<int>();
 
            // No boxing, no casting:
            list1.Add(3);
 
            // Compile-time error:
            // list1.Add("It is raining in Redmond.");

 

En el código de cliente, la única sintaxis que se agrega con List<T> en comparación con ArrayList es el argumento de tipo en la declaración y creación de instancias. A cambio de esta complejidad de codificación ligeramente mayor, se puede crear una lista que no sólo es más segura que ArrayList, sino que también es bastante más rápida, en especial cuando los elementos de lista son tipos de valor.

 

Parámetros de tipos genéricos

En una definición de tipo o método genérico, un parámetro de tipo es un marcador para un tipo especificado por un cliente al crear una instancia de una variable del tipo genérico. Una clase genérica, como GenericList<T>, que se muestra en Introducción a los genéricos, no se puede utilizar tal cual, porque no es realmente un tipo, sino el plano de un tipo. Para utilizar GenericList<T>, el código de cliente debe declarar y crear una instancia de un tipo construido especificando un argumento de tipo dentro de los corchetes angulares. El argumento de tipo para esta clase determinada puede ser cualquier tipo reconocido por el compilador. Se puede crear cualquier número de instancias del tipo construido, cada una de ellas con un argumento de tipo diferente, de la forma siguiente:

 

GenericList<float> list1 = new GenericList<float>();
            GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
            GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

 

En cada una de estas instancias de GenericList<T>, cada aparición de T en la clase se sustituirá en tiempo de ejecución con el argumento de tipo. Mediante esta sustitución, hemos creado tres objetos independientes y eficaces con seguridad de tipos utilizando una sola definición de clase. Para obtener más información sobre cómo el CLR realiza esta sustitución, vea Genéricos en el motor en tiempo de ejecución.

Instrucciones de nomenclatura de parámetros de tipo

·         Denomine los parámetros de tipo genérico con nombres descriptivos, a menos que un nombre de una sola letra sea muy fácil de entender y un nombre descriptivo no agregue ningún valor.

C#

        public interface ISessionChannel<TSession> { /*...*/ }
        public delegate TOutput Converter<TInput, TOutput>(TInput from);
        public class List<T> { /*...*/ }

·         Considere el uso de T como nombre del parámetro de tipo para los tipos con un parámetro de tipo de una sola letra.

C#

        public int IComparer<T>() { return 0; }
        public delegate bool Predicate<T>(T item);
        public struct Nullable<T> where T : struct { /*...*/ }

·         Añada el prefijo "T" a los nombres de parámetros de tipo descriptivos.

C#

            public interface ISessionChannel<TSession>
            {
                TSession Session { get; }
            }

·         Considere indicar las restricciones de un parámetro de tipo en el nombre del parámetro. Por ejemplo, un parámetro restringido a ISessionse puede denominar TSession.

 

Restricciones de tipos de parámetros

Cuando se define una clase genérica, se pueden aplicar restricciones a las clases de tipos que el código de cliente puede usar para argumentos de tipo cuando crea una instancia de la clase. Si el código de cliente intenta crear una instancia de la clase con un tipo que no está permitido por una restricción, el resultado es un error de compilación. Estas limitaciones se llaman restricciones. Las restricciones se especifican mediante la palabra clave contextual where. En la siguiente tabla se muestran los seis tipos de restricción:

Restricción

Descripción

where T: struct

El argumento de tipo debe ser un tipo de valor. Se puede especificar cualquier tipo de valor excepto Nullable. Para obtener más información, consulte Utilizar tipos que aceptan valores NULL.

where T : class

El argumento de tipo debe ser un tipo de referencia; esto se aplica también a cualquier tipo de clase, interfaz, delegado o matriz.

where T : new()

El argumento de tipo debe tener un constructor público sin parámetros. Cuando se utiliza la restricción new() con otras restricciones, debe especificarse en último lugar.

where T : <nombre de clase base>

El argumento de tipo debe ser la clase base especificada, o bien debe derivarse de la misma.

where T: <nombre de interfaz>

El argumento de tipo debe ser o implementar la interfaz especificada. Se pueden especificar varias restricciones de interfaz. La interfaz con restricciones también puede ser genérica.

where T : U

El argumento de tipo proporcionado para T debe ser o derivar del argumento proporcionado para U.

Por qué utilizar restricciones

Si desea examinar un elemento en una lista genérica para determinar si es válido o compararlo con otro elemento, el compilador debe tener alguna garantía de que el operador o método que tiene que llamar será compatible con cualquier argumento de tipo que el código de cliente pudiera especificar. Esta garantía se obtiene al aplicar una o más restricciones a la definición de clase genérica. Por ejemplo, la restricción de clase base le indica al compilador que sólo los objetos de este tipo o derivados de éste se usarán como argumentos de tipo. Una vez que el compilador tiene esta garantía, puede permitir que se llame a los métodos de ese tipo en la clase genérica. Las restricciones se aplican mediante la palabra clave contextual where. En el siguiente ejemplo de código se muestra la funcionalidad que se puede agregar a la clase GenericList<T> (en Introducción a los genéricos) mediante la aplicación de una restricción de clase base.

 

public class Employee
        {
            private string name;
            private int id;
 
            public Employee(string s, int i)
            {
                name = s;
                id = i;
            }
 
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
 
            public int ID
            {
                get { return id; }
                set { id = value; }
            }
        }
 
        public class GenericList<T> where T : Employee
        {
            private class Node
            {
                private Node next;
                private T data;
 
                public Node(T t)
                {
                    next = null;
                    data = t;
                }
 
                public Node Next
                {
                    get { return next; }
                    set { next = value; }
                }
 
                public T Data
                {
                    get { return data; }
                    set { data = value; }
                }
            }
 
            private Node head;
 
            public GenericList() //constructor
            {
                head = null;
            }
 
            public void AddHead(T t)
            {
                Node n = new Node(t);
                n.Next = head;
                head = n;
            }
 
            public IEnumerator<T> GetEnumerator()
            {
                Node current = head;
 
                while (current != null)
                {
                    yield return current.Data;
                    current = current.Next;
                }
            }
 
            public T FindFirstOccurrence(string s)
            {
                Node current = head;
                T t = null;
 
                while (current != null)
                {
                    //The constraint enables access to the Name property.
                    if (current.Data.Name == s)
                    {
                        t = current.Data;
                        break;
                    }
                    else
                    {
                        current = current.Next;
                    }
                }
                return t;
            }
        }

 DEMOSTRACIÓN

En un aplicativo de consola, vamos a escribir dos clases DataMember y ParClaveValor


Posteriormente añadiremos otra clase denominandola DataStore


En el metodo Main, instanciaremos la primeras clase y le pasamos los valores, de tipo string para la primera y de tipo int para la segunda y volvemos instanciar la segunda clase pasando en la clave valor como se muestra en la siguiente figura:


Instanciaremos luego la clase DataStore pasando los valores en los metodos AddOrUpdate:


El resultado a continuación:




Tal vez te interesen estas entradas

No hay comentarios