sábado, 2 de noviembre de 2013

La palabra reservada default(T)



Dentro del universo de Generics, existe un concepto que no suele ser demasiado conocido y que aunque puede ser sustituido por otro tipo de técnicas, ahorra mucho código y lo hace mucho más seguro y entendible para terceros.


Hablamos de la palabra reservada default(T), de la que hablaremos en este post para intentar darte un enfoque no solo teórico sino también práctico, en pos de intentar que sea mucho más familiar para ti, de ahora en adelante en tus desarrollos de Generics.




Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.


·         Generics
o   La palabra Reservada default(T)



Default(T)

Es una palabra reservada que nos devuelve el valor por defecto del tipo facilitado por parámetros.

Los valores por defecto dentro de los tipos más importantes de CLR son los siguientes:

  • Clases .- null.
  • Nullable<T> .- null.
  • Estructuras numéricas (int, double, decimal, etc) .- 0.
  • Estructuras (DateTime) .- 01/01/0001.
  • Estructuras (Char) .- Carácter vacío.
  • Estructuras (Bool) .- false.


Puede parecer algo muy poco útil, pero en Genéricos sin ningún tipo de restricción nos presta una buena ayuda.

Si vemos nuestro primer ejemplo de Generics:

public static void CambiarValores<T>(ref T a, ref T b)
{
    T _a = a;
    T _b = b;

    a = _b;
    b = _a;
}


Podríamos querer comprobar que los valores que se habían introducido por parámetros, corresponden a variables inicializadas o con datos. Al no existir ninguna restricción en la firma del método, el tipo de estos valores puede corresponder a cualquiera del CLR, por lo que o nos hacemos un método que haga esta comprobación, por ejemplo con un swich que pregunte por cada uno de los tipos posibles, o utilizamos la palabra reservada default(T).


Vamos a ver un ejemplo un poco más representativo y que nos van a aclarar un poco más las ideas.



Ejemplo

En nuestro ejemplo, vamos a ver un método que podremos utilizar para realizar una pequeña comprobación dentro de una colección de datos, y que usaremos en cualquier cliente gráfico (Windows Forms, WPF, ASP, Silverlight, etc) para pre visualizar dentro de un Listbox, que valores de la colección tienen datos instanciados y cuales tienen el valor por defecto. Para que se vea bien pintaremos la celda con datos con valores instanciados en verde y en rojo las que tengan datos por defecto.

El ejemplo lo he realizado con un ListBox de WPF y un objeto Converter para realizar los pintados.

Para hacer el ejemplo me he apoyado en una clase genérica info, muy sencilla con 2 propiedades, una de tipo T, para guardar el valor que lleva el elemento a comprobar y otra bool, donde guardaré si el valor de T de este elemento concuerda con el valor por defecto del tipo T.

public class DatoInfo<T>
{
    public T    Value     { get; set; }
    public bool IsDefault { get; set; }
}


El método que realiza la comprobación es el siguiente:

public List<DatoInfo<T>> ComprobadorDefaultToBool<T>(List<T> sourceList)
{
    List<DatoInfo<T>> resultado = new List<DatoInfo<T>>();

    string tDefaultValue = default(T) == null ? "vacio" : default(T).ToString();

    foreach (var t in sourceList)
    {
        string tValue = t == null ? "vacio" : t.ToString();

        bool isDefault = tValue == tDefaultValue;

        resultado.Add( new DatoInfo<T>() { Value = t, IsDefault = isDefault });
    }

    return resultado;
}


Simplemente recibe una colección genérica y recorre sus elementos para comprobar si corresponden con el valor por defecto para su tipo.


Nota: Como podemos comprobar he tenido que realizar sendas sentencias ternarias para convertir en string el valor del item de la colección y el valor por defecto. Esto lo he realizado así porque el framework no deja comparar directamente sus valores cuando son de tipo genérico.











Ahora iremos mostrando las llamadas y los resultados con todas las colecciones genéricas:

List<int> lista         = new List<int>() { 1, new int(), new int(), 1 };
var listaConvertidaBool = this.ComprobadorDefaultToBool(lista);















List<int> lista         = new List<int>() { 1, new int(), new int(), 1 };
var listaConvertidaBool = this.ComprobadorDefaultToBool(lista);
















List<DateTime> lista    = new List<DateTime>() { DateTime.Today, DateTime.Today.AddDays(1), new DateTime(), new DateTime() };
var listaConvertidaBool = this.ComprobadorDefaultToBool(lista);
















List<char> lista        = new List<char>() { 'A', new char(), 'B', new char() };
var listaConvertidaBool = this.ComprobadorDefaultToBool(lista);
















List<bool> lista        = new List<bool>() { true, new bool(), new bool(), true };
var listaConvertidaBool = this.ComprobadorDefaultToBool(lista);
















List<int?> lista        = new List<int?>() { new int?(), 99, 99, new int?() };
var listaConvertidaBool = this.ComprobadorDefaultToBool(lista);

















Instanciación

Me gustaría también puntualizar que la palabra clave default(T) también es muy útil cuando queremos instanciar una variable de tipo T dentro de nuestros métodos genéricos, sin tener que hacer uso de Reflection.

public void EjemploGenerico<T>(T data)
{
    // T nueva_variable_tipo_T_reflection = Activator.CreateInstance<T>();

    T nueva_variable_tipo_T = default(T);
}






Pues hasta aquí la palabra reservada default(T). Aquí dejo el código por si alguien quiere echarle un ojo.