sábado, 9 de septiembre de 2017

Curso de C#: Indizadores y sobrecarga de operadores

Indizadores

Un indizador es un concepto más o menos nuevo. Se trata de una simplificación de un objeto que es en realidad un array o una colección.
A modo de ejemplo se muestra un caso concreto. Un libro no es más que un objeto que contiene una serie de capítulos, para construir el objeto Libro con una colección Capítulos dentro de él, en la que sea posible añadir o modificar capítulos, de modo que necesita un método Add y un método Modify y luego llamar a estos métodos desde el código cliente:
static void Main()
{
    Libro miLibro=new Libro();

    miLibro.Capitulos.Add("Las tres crisis");
    miLibro.Capitulos.Add("Cosmos");
    miLibro.Capitulos.Modify("El mundo y sus demonios",1);

    ...
}
Un indizador, permite tratar al objeto como un array o una colección, haciendo la codificación más intuitiva. El código equivalente al anterior sería así:
static void Main()
{
    Libro miLibro=new Libro();

    miLibro[0]=" Los dragones del edén ";
    miLibro[1]="Cosmos";
    miLibro[1]=" El mundo y sus demonios";

    ...
}
Curso de C#: Indizadores y sobrecarga de operadores



Este código es más natural: pues el objeto Libro no es más que un conjunto de capítulos, y conviene tratarlo como un array, independientemente de que el objeto permita ofrecer también otra serie de propiedades, métodos y demás. Esta es su sintaxis
class Libro
{
    public object this[int index]
    {
        get
        {
            ...
        }
        set
        {
            ...
        }
    }
    ...
}
El indizador se construye de un modo muy similar a las propiedades, pero  el nombre de esta "propiedad" es el propio objeto, es decir, this, el tipo, lógicamente, es object y, además, requiere un argumento entre corchetes, que sería el índice del elemento al que queremos acceder. El ejemplo del libro completo:
    class Libro
    {
        private ArrayList capitulos=new ArrayList();

        public object this[int indice]
        {
            get
            {
                if (indice >= capitulos.Count || indice < 0)
                    return null;
                else
                    return capitulos[indice];
            }
            set
            {
                if (indice >= 0 && indice < capitulos.Count)
                    capitulos[indice]=value;
                else if (indice == capitulos.Count)
                    capitulos.Add(value);
                else
                    throw new Exception("No se puede asignar a este elemento");
            }
        }  

      public int NumCapitulos
      { 
   
        get
             {
                      Return capítulos.Count;
               }
            }     
   

     class IndizadorLibros
    {
        static void Main()
        {
            Libro miLibro=new Libro();

            miLibro[0]="EGB Un tereno y grácil bucle";
            miLibro[1]="Yo soy un extraño bucle";
            miLibro[1]="La nueva mente del emperador";

            for (int i=0;i<miLibro.NumCapitulos;i++)
                Console.WriteLine("Capitulo {0}: {1}",i+1,miLibro[i]);

            string a=Console.ReadLine();
        }
    }
}
La propiedad NumCapitulos devuelve el número de capítulos que hay incluidos en el objeto Libro hasta este momento. Un array tiene ya la propiedad Length que devuelve exactamente lo mismo, y una colección tiene ya la propiedad Count, que hace también lo mismo. La razón de crear esta propiedad es porque Libro es una clase, no una colección ni un array. Al implementar un indizador, se puede acceder a los elementos de los objetos de esta clase como si fuera un array, pero el compilador no genera una propiedad Count o Length. Por este motivo, si deseamos una propiedad que ofrezca el número de elementos contenidos en el objeto será necesario implementarla nosotros mismos. En este caso se llama NumCapitulos.
También se ha declarado el campo privado "capitulos" del tipo System.Collections.ArrayList. Debido a que es necesario meter los capítulos en algún array o en alguna colección (en este caso, se trata de una colección).
El hecho de implementar un indizador no convierte a una clase en una colección o en un array, pero ofrece una interfaz similar a estos. En definitiva, el indizador hace que podamos "encubrir" dicha colección en aras de obtener una lógica más natural y manejable.

Sobrecarga de operadores

Sobrecargar un operador consiste en modificar su comportamiento cuando este se utiliza con una determinada clase.
Es perfectamente factible sumar dos números de tipo int, o dos de tipo short, e incluso se pueden sumar dos números de tipos distintos. Pero, si en una variable hay almacenadas cantidades en metros y en otra variables están almacenadas en centímetros ¿Cómo las sumo?  El resultado de sumarlas directamente sería incorrecto.
double m=10;
double c=10;

double SumaMetros=m+c;
double SumaCentimetros=m+c;

Console.WriteLine(SumaMetros);
Console.WriteLine(SumaCentimetros);
El ordenador no sabe nada de metros ni centímetros, por lo que el resultado sería 20 en ambos casos. Y por supuesto, es incorrecto, porque 10 metros más 10 centímetros serán 10.1 metros, o bien 1010 centímetros, pero en ningún caso será el 20. Una posibilidad  de resolver este problema sería crear una clase metros con un método que se llamara SumarCentimetros, para que hiciera la suma correcta. Pero el uso de esta clase sería un poco forzado, pues para sumar  utilizaremos el operador + en lugar de tener que invocar un método específico.
Aquí es donde resulta útil la sobrecarga de operadores. Si es posible  sobrecargar el operador + en la clase metros para que haga una suma correcta independientemente de las unidades que se le sumen, se habrá resuelto el problema. Ejemplo.
Metros m=new Metros(10);
Centimetros c=new Centimetros(10);

Metros SumaMetros=m+c;
Centimetros SumaCentimetros=c+m;

Console.WriteLine(SumaMetros.Cantidad);
Console.WriteLine(SumaCentimetros.Cantidad);
Y el resultado será correcto: 10.1 metros y 1010 centímetros, sin necesidad de convertir previamente los centímetros a metros y viceversa. La sintaxis de un operador + sobrecargado es la siguiente:
[acceso] static NombreClase operator+(Tipo a[, Tipo b])
{
      ...
}
El modificador de acceso en caso de que proceda, con la precaución de que el modificador de acceso de un operador sobrecargado no puede ser de un ámbito mayor que el de la propia clase, es decir, si la clase es internal, el operador sobrecargado no puede ser public, puesto que su nivel de acceso sería mayor que el de la clase que lo contiene. Después la palabra static. Lógicamente, tiene que ser static puesto que el operador está sobrecargado para todos los objetos de la clase, y no para una sola instancia en particular. Después el nombre de la clase, lo cual implica que hay que retornar un objeto de esta clase. Después la palabra "operator" seguida del operador que deseamos sobrecargar (deben ir juntos sin separarlos con ningún espacio en blanco) y, a continuación, la lista de argumentos entre paréntesis. Si el operador a sobrecargar es unitario, necesitamos un único argumento, pero si es binario necesitaremos dos. Ejemplo:
public class Metros
{
    private double cantidad=0;

    public Metros() {}

    public Metros(double cant)
    {
        this.cantidad=cant;
    }

    public double Cantidad
    {
        get
        {
            return this.cantidad;
        }
        set
        {
            this.cantidad=value;
        }
    }

    public static Metros operator+(Metros m, Centimetros c)
    {
        Metros retValue=new Metros();
        retValue.Cantidad=m.Cantidad+c.Cantidad/100;

        return retValue;
    }
}
La sobrecarga del operador está definid abajo y en negrita. Esto es un método que se ejecutará cuando el compilador se tope con una suma de dos objetos, el primero de la clase Metros y el segundo de la clase Centímetros. Por ejemplo, si "m" es un objeto de la clase Metros, y "c" un objeto de la clase Centimetros, la línea:
Metros SumaMetros=m+c;
Haría que se ejecute el método anterior. Es decir, se crea un nuevo objeto de la clase Metros (el objeto retValue). En su propiedad Cantidad se suman lo que valgan las propiedades Cantidad del argumento m y la centésima parte de la propiedad Cantidad del argumento c, retornando al final el objeto con la suma hecha, objeto que se asignaría, por lo tanto, a SumaMetros. Es decir, al haber sobrecargado el operador + en la clase Metros, cuando el compilador se encuentra con la suma, hace lo que hayamos implementado en el método, en lugar de la suma normal. Dicho de otro modo, se modificado el comportamiento del operador +.
Si nos encontramos con una resta en lugar de una suma
Metros SumaMetros=m-c;

El compilador dará un error, ya que el operador - no está sobrecargado y, por lo tanto, es incapaz de efectuar la operación. Por lo tanto, para que se pudiera efectuar la resta, hay  que sobrecargar también el operador -:

    public static Metros operator-(Metros m, Centimetros c)
    {
        Metros retValue=new Metros();
        retValue.Cantidad=m.Cantidad-c.Cantidad/100;

        return retValue;
    }
Lo mismo se puede hacer con los operadores * y /, para que también se hicieran correctamente las multiplicaciones y divisiones.
Si el compilador si se encuentra con esta  línea:
Centimetros SumaCentimetros=c+m;
Volvería a dar error, pues la sobrecarga en la clase Metros afecta a las sumas cuando el primer operando es de la clase Metros y el segundo es de la clase Centímetros. Si añadimos otra sobrecarga del operador + en la clase Metros en la que los centímetros vayan en el primer argumento y los metros en el segundo. Seguiría dando error, pues SumaCentimetros no es un objeto de la clase Metros, sino de la clase Centimetros. Lo más adecuado sería sobrecargar el operador de ese modo en la clase Centimetros y no en la clase Metros.
El operador “=” no se puede sobrecargar, por lo que para comparar directamente centímetros con metros hay que usar las conversiones definidas.
Estos son los operadores que se permite sobrecargar en C#:
Unitarios: +. -, !, ~, ++, --, true, false

Binarios: +, -, *, /, %, &, |, ^,<<, >>, ==, !=, >, <, >=, <=

No hay comentarios:

Publicar un comentario