sábado, 19 de marzo de 2022

Curso avanzado de C#. Propiedades Indexadas, Tipos genéricos y Métodos genéricos

Las propiedades indexadas, o indexadores, se comportan de manera un poco diferente a las propiedades estándar. El propósito principal de las propiedades indexadas es permitir el "acceso a grupos de elementos de forma similar a como se accede a un array". En otras palabras, si tenemos clases que utilizan matrices u otros tipos de colecciones, debemos considerar el uso de indizadores para acceder a los valores de estas colecciones internas. Las propiedades estándard discutidas hasta ahora se utilizan para acceder a valores individuales en clases, mientras que una propiedad indexada Se utilizará para encapsular un conjunto de valores.

Para mostrar el funcionamiento de las propiedades indexadas, necesitamos un ejemplo simple que lo ilustre. Una aplicación que trate con direcciones de red en un escenario de subredes IP. 

Curso avanzado de C#. Propiedades Indexadas, Tipos genéricos y Métodos genéricos



Queremos crear una clase que contenga una matriz de 32 valores para almacenar direcciones IP. Cada la matriz podría representar una dirección IP específica estableciendo los elementos en un 1 o un 0 y luego utilizándolos para representar una dirección IP. Ahora creamos una clase direcciónIP y  una propiedad indexada dentro la clase para almacenar la matriz de bits que se utilizará para almacenar la dirección IP.

public class DireccionIP
    {
        private int[] ip;
        public int this[int index]
        {
            get
            {
                return ip[index];
            }
            set
            {
                if (value == 0 || value == 1)
                    ip[index] = value;
                else
                    throw new Exception("Invalid value");
            }
        }
    }
  class Programa
    {
        static void Main(string[] args)
        {
            DireccionIP miIP = new DireccionIP(); // inicializa la       direccion  IP con ceros
            for (int i = 0; i < 32; i++)
            {
                miIP[i] = 0;
            }
        }
    }

Esta clase tiene solo una  propiedad, que es una propiedad indexada para almacenar los 32 bits de una dirección IP. El diferenciador clave que hace que esta propiedad sea indexada es, que en vez de un nombre de propiedad, tiene la palabra clave “this”. Además la propiedad parece aceptar un parámetro, pero en realidad está aceptando un índice para el parámetro “value”. Si colocamos este código en una aplicación de consola de C # y lo ejecutamos, rellenará la propiedad de la matriz int con todo ceros. 

Proponemos agregar código para actualizar los valores de los valores de índice arbitrarios e inspeccionaremos la matriz a través de la depuración para ver cómo se rellenan los valores. Pero antes hay que tener en cuenta algunos puntos clave para las propiedades indexadas: 

Aceptan un valor de índice en lugar del parámetro de valor de propiedad estándar.
Se identifican mediante el uso de la palabra clave this. 
Las propiedades indexadas se pueden crear en clases o estructuras.
Sólo puede existir una propiedad indexada en una sola clase o estructura.
Pueden contener métodos Get y Set como otras propiedades. 

Tipos genéricos y Métodos genéricos 


Los métodos genéricos permiten diseñar clases y métodos sin especificar los tipos hasta la declaración e instanciación. La ventaja de hacerlo es una reducción del boxing y unboxing o conversiones explicitas (casting) en tiempo de ejecución. Los tipos genéricos funcionan tanto para los tipos pasados por valor como por referencia.

Otras ventajas de los tipos genéricos son; la reutilización de código, la seguridad de tipos y la eficiencia.

Si consideramos utilizar diferentes tipos de colección en nuestro código que actúen sobre los objetos. Por ejemplo, la clase  Cola, Permite almacenar objetos  utilizando el método Encolar () y sacarlos con el método Desencolar (). Sin embargo, ¿qué sucede si queremos almacenar elementos de diferentes tipos como int o char? Como el objeto  Cola está diseñado para contener tipos por referencia, es necesario utilizar el boxing y unboxing para los tipos pasados por valor. Si estamos almacenando objetos en la cola, es necesario utilizar conversiones explícitas para esos tipos de referencia cuando se retiran de la cola. Este código será propenso a errores.

En lugar de realizar la la conversión de tipos, que además tiene un impacto en el rendimiento, podemos utilizar clases genéricas con un parámetro genérico que acepta un tipo de clase en tiempo de ejecución. Esto también ahorra la necesidad de crear múltiples clases que implementen la misma funcionalidad para cada tipo que necesitemos soportar.

Definición de tipos genéricos 

La definición de los tipos genéricos se realiza mediante el uso de un parámetro de tipo genérico entre paréntesis angulares, <T>. T es solo la representación estándar para tipos genéricos que se utiliza en la mayoría de la documentación relacionada con genéricos Podemos utilizar una letra de nuestra elección. A continuación se muestra un ejemplo de una clase  genérica: 

// ejemplo de cola de estilo genérico

  public class PilaGenerica<T>
    {
        public void Introducir(T obj);
        public T Sacar();
    }

Aunque aquí se muestra una clase llamada PilaGenerica <T> para diferenciar una pila de la Clase Pila .NET, debemos mirar el espacio de nombres System.Collection.Generic para determinar si .NET Framework ya contiene una clase Pila genérica. Siempre que sea posible conviene reutilizar el  código existente. Nuestros tipos genéricos actúan como otros tipos de referencia y podemos incluir constructores, variables miembro, y métodos. Los métodos, incluido el constructor, también pueden incluir parámetros de tipo. 

Usando Tipos Genéricos


La creación de la clase PilaGenerica  permite pasar el tipo de objeto que esta clase puede almacenar en la pila cuando se crea una instancia del objeto.  A continuación un ejemplo de creación de instancias de un objeto PilaGenerica para Almacenar objetos de tipo  Estudiante:

// Pila genérica que se utilizará para almacenar objetos de tipo Estudiante
PilaGenerica<Estudiante> PilaEstudiante = new PilaGenerica<Estudiante>();

Estudiante miEstudiante = new Estudiante("Juan", "Martinez");

// almacenamos el objeto miEstudiante en la PilaEstudiante

PilaEstudiante.Introducir(miEstudiante);
// recupera el objeto miEstudiante de la PilaEstudiante
PilaEstudiante.Sacar();

Al utilizar tipos genéricos, esta clase pila permite añadir y sacar objetos Estudiante de la pila sin necesidad de conversión explícita porque el tipo de referencia se especifica durante la creación de las instancias.

Definiendo Métodos Genéricos

Los métodos genéricos también se declararán con parámetros de tipo. Esto significa que, al igual que la firma de clase, la firma del método utilizará un marcador de posición para el tipo que será pasado al método. De la misma manera que las clases genéricas son seguras para el tipo y no requieren empaquetamiento /desempaquetamiento o conversiones explícitas, los métodos genéricos también comparten esta misma característica. Uno de los ejemplos más sencillos que existen, son los métodos de intercambio.

El intercambio es una función comúnmente utilizada en algoritmos de clasificación simples:

// ejemplo de método genérico con parámetros por tipo
    public void Intercambio<T>(ref T valorUno, ref T valorDos)
    {
        T temp = valorUno;
        valorUno = valorDos;
        valorDos = temp;
    }

En la firma del Método todavía utilizamos modificadores de acceso y tipos de retorno. En este caso, hacemos público el método y establecemos el tipo de retorno como nulo. Luego nombramos la función como Intercambio, y del mismo modo que en una clase genérica, utilizaremos  corchetes angulares y un marcador de posición de tipo <T>.

Aquí es donde el método genérico difiere ligeramente de la versión no genérica. Los parámetros utilizan el palabra clave ref y el tipo de marcador de posición. La palabra clave ref significa que los argumentos pasados serán enviados por referencia. El método actúa sobre los valores reales pasados como argumentos a través de una referencia a una dirección de memoria.  El marcador de posición T significa que los argumentos serán de tipo T, en función del tipo utilizado en el momento en que se llame al método. Puede ser tipos por valor o tipos por referencia. 

Observamos también que la variable local temp del método también se declara con el tipo T. Esto tiene mucho sentido porque necesitaremos utilizar los mismos tipos durante el proceso de intercambio, y temp es solo una variable local que se puede utilizar para almacenar temporalmente un valor antes asignándolo a la segunda variable. 

Utilizando Métodos Genéricos

Al usar métodos genéricos, pasamos el tipo correcto a la llamada al método, reemplazando el parámetro T con el tipo que utilizamos para el intercambio. Si tenemos una matriz de valores y queremos llamar al método  Intercambio para ordenar la matriz. La matriz podría ser de casi cualquier tipo y lo ideal sería que ya viniera pre-clasificada, de modo que  nos permitiría poder clasificar los tipos según su valor, en el ejemplo clasificaremos enteros: 

public class Programa
{
    static void Main(string[] args)
    {
        int[] arrEnteros = new int[] { 2, 5, 4, 7, 6, 7, 1, 3, 9, 8 };
        char[] arrChar = new char[] { 'f', 'a', 'r', 'c', 'h' };
        // Ordenación de enteros

        for (int i = 0; i < arrEnteros.Length; i++)
        {
            for (int j = i + 1; j < arrEnteros.Length; j++)
            {
                if (arrInts[i] > arrEnteros[j])
                {
                    Intercambio<int>(ref arrEnteros[i], ref arrEnteros[j]);
                }
            }
        }
 // Ordenación de caracteres

        for (int i = 0; i < arrChar.Length; i++)
        {
            for (int j = i + 1; j < arrChar.Length; j++)
            {
                if (arrChar[i] > arrChar[j])
                {
                    Intercambio<char>(ref arrChar[i], ref arrChar[j]);
                }
            }
        }

    }

    public void Intercambio<T>(ref T valorUno, ref T valorDos)
    {
        T temp = valorUno;
        valorUno = valorDos;
        valorDos = temp;
    }
}   

Podríamos haber escrito la funcionalidad de intercambio dentro del bucle anidado pues  es relativamente simple,  pero este ejemplo sirve para mostrar cómo usar un  Método genérico como caja negra que aceptará cualquier tipo de objeto que queramos comparar.
La primera matriz contiene enteros y funciona bien la ordenación por el método de la burbuja del método Intercambio pasado como tipo int. 
La segunda matriz consta de caracteres del tipo char. Cuando es llamado el método de intercambio se pasa el tipo de carácter como el tipo sobre el que se actuará. Los valores de char se comparan utilizando su código numérico. Este ejemplo muestra cómo se pueden utilizar los  métodos genéricos con diferentes tipos. 

No hay comentarios:

Publicar un comentario