Al pan Enum y al vino Flags

Mientras preparaba el material para el WebCast que di para el dotNet Club de la U. Lleida, me tope con enumeradores como marcadores de bit y quiero comentar lo útil que pueden llegar a ser.

Básicamente tenemos dos opciones o características que determinan el comportamiento de este tipo de dato constante y se basa en el uso o no del atributo FlagAttribute sobre el Enum. Es importante entender que dicho atributo será especialmente útil cuando necesitemos que los valores del enumerador se combinen a través de operaciones lógicas o bit a bit del tipo AND, OR, NOT y XOR.

Un ejemplo de típica declaración de enumeradores seria:

   1: public enum TypicalEnums
   2: {
   3:     Option1, // = 0x00
   4:     Option2, // = 0x01
   5:     Option3, // = 0x02
   6:     Option4, // = 0x03
   7:     Option5, // = 0x04
   8:     Option6  // = 0x05
   9: }

En este ejemplo, damos por supuesto que una variable de tipo TypicalEnums únicamente podrá contener un único valor, cuyo tipo subyacente es por defecto es int. Sin embargo el siguiente enumerador:

   1: [Flags]
   2: public enum FlagEnums
   3: {
   4:     Option0 = 0x00,     // 0 = 0x00
   5:     Option1 = 0x01,     // 1 = 0x01
   6:     Option2 = 0x01 << 1,// 2 = 0x02
   7:     Option3 = 0x01 << 2,// 4 = 0x04
   8:     Option4 = 0x01 << 3,// 8 = 0x08
   9:     Option5 = 0x01 << 4,//16 = 0x16
  10:     Option6 = 0x01 << 5 //32 = 0x32
  11: }

Nos permitirá tener varios valores dentro de una misma variable del tipo FlagsEnums.

En el primer caso podremos hacer por ejemplo:

Cliente.Tipo = TypicalEnums.Option1 | TypicalEnums.Option2

Pero no tendrá ninguna repercusión pues Cliente.Tipo almacenará el valor 1 (TypicalEnums.Option1), pero sin embargo la siguiente sentencia:

Cliente.Caracteristicas = FlagEnums.Option2 | FlagEnums.Option5,

Si que almacenará ambas opciones.

Fijaros que en la declaración de FlagsEnum hemos indicado explícitamente el valor de las opciones de tres formas, desplazando un bits a la izquierda en base al valor 0x01, con valores numéricos decimales y valores hexadecimales. Todos ellos tendrán la siguiente correspondencia en binario:

  • Option0 = 0000000
  • Option1 = 0000001
  • Option2 = 0000010
  • Option3 = 0000100
  • Option4 = 0001000
  • Option5 = 0010000
  • Option6 = 0100000

A partir de aquí todo lógica. Si en el anterior ejemplo asignamos FlagsEnum.Option2 y FlagsEnum.Option5 al campo Cliente.Característica el valor que almacenará será 7 es decir 2 + 5, y por tanto utilizaremos el operador lógico OR:

   1: Cliente cliente = new Cliente
   2:                       {
   3:                           IdProvincia = 1,
   4:                           Tipo = TypicalEnums.Option1, 
   5:                           Caracteristicas = FlagEnums.Option2 | FlagEnums.Option5,
   6:                           //Caracteristicas = 00000010        | 00001000           = 00001010
   7:                           Nombre = "Cliente 1",
   8:                           VolumenNegocio = 10.0m
   9:                       };

Ahora bien,¿cómo podemos sacar el máximo provecho de los enumeradores con marcadores de bit? pues aplicando lógica, es decir, si queremos saber si un marcador se ha establecido utilizaremos AND:

   1: if ((cliente.Caracteristicas & FlagEnums.Option5) == FlagEnums.Option5)
   2:     // (00001010 & 00001000) = 00001000 
   3:     //             00001000  = 00001000 => true
   4:     Console.WriteLine("{0}: nFlagEnum t[{1}] nOption 5 t[{2}] nHence: tt[{3}]",
   5:                       cliente.Nombre,
   6:                       Convert.ToString(((int)cliente.Caracteristicas), 2),
   7:                       Convert.ToString(((int)FlagEnums.Option5), 2),
   8:                       Convert.ToString((int)(cliente.Caracteristicas & FlagEnums.Option5), 2));

Como puedes ver, en la condición de la sentencia if estamos comprobando la existencia de FlagsEnums.Option5 de forma lógica. Realizamos la operación lógica a nivel de bit de Cliente.Característica AND FlagsEnums.Option5 e igualamos al valor de FlagsEnums.Option5. Por otro lado:

   1: if ((cliente.Caracteristicas & FlagEnums.Option4) != FlagEnums.Option4)
   2:     // (00001010 & 00000100) = 00000100 
   3:     //             000000000 = 00000100 => false
   4:     Console.WriteLine("Cliente with nFlagEnum t[{0}] nhasn't Option 4 t[{1}]",
   5:         Convert.ToString(((int)cliente.Caracteristicas), 2),
   6:         Convert.ToString(((int)FlagEnums.Option4), 2));

Si queremos comprobar FlagsEnums.Option4, el cual no está, el razonamiento será el mismo (fíjate en las líneas 2 y 3). Tras ejecutar ambos Snippets con la clase Cliente definida como:

   1: public class Cliente
   2: {
   3:     public int IdProvincia { get; set; }
   4:     public TypicalEnums Tipo { get; set; }
   5:     public FlagEnums Caracteristicas { get; set; }
   6:     public string Nombre { get; set; }
   7:     public decimal VolumenNegocio { get; set; }
   8:  
   9:     public override string ToString()
  10:     {
  11:         return string.Format("Cliente: {0} - Provincia:{1} - nTipo: {2} - Caract:{3} - Vol.:{4}en",
  12:                              Nombre, IdProvincia, (int) Tipo,Caracteristicas, VolumenNegocio);
  13:     }
  14: }

Obtendremos:

image

Por último, si pretendemos quitar un marcador ya establecido utilizaremos un XOR de forma que:

cliente.Caracteristicas = cliente.Caracteristicas ^ FlagEnums.Option5;

Desasignaría FlagsEnums.Option5 al campo Cliente.Características.

Uso de métodos extensores

Soy un auténtico fan de este tipo de característica y no puedo dejar pasar ni un solo enumerador de marcadores de bit sin extender el típico método HasFlag o como_queráis_llamarlo a la clase que los utiliza, siempre y cuando sea posible, y por tanto una forma de extender la clase Cliente para la comprobación de marcadores seria la que he utilizado para confeccionar el ejemplo de este post, es decir:

   1: public static class ClienteExtensions
   2: {
   3:     public static string HasFlagVerbose(this Program.Cliente cliente, Program.FlagEnums flag)
   4:     {
   5:         return string.Format("Checking {2} for {0}: nCaracteristica t[{1}] n{2} t[{3}] nHence tt[{4}]n",
   6:                              cliente.Nombre,
   7:                              Convert.ToString(((int)cliente.Caracteristicas), 2).PadLeft(8, '0'),
   8:                              flag,
   9:                              Convert.ToString(((int)flag), 2).PadLeft(8, '0'),
  10:                              Convert.ToString((int)(cliente.Caracteristicas & flag), 2).PadLeft(8, '0'));
  11:     }
  12:  
  13:     public static bool HasFlag(this Program.Cliente cliente, Program.FlagEnums flag)
  14:     {
  15:         return (cliente.Caracteristicas & flag) == flag;
  16:     }
  17: }

Con lo que el cuerpo del programa que he ejecutado para mostrar los resultados seria:

   1: public class Program
   2:     {
   3:         
   4:         static void Main(string[] args)
   5:         {
   6:             Cliente cliente = new Cliente
   7:                                   {
   8:                                       IdProvincia = 1,
   9:                                       Tipo = TypicalEnums.Option1, 
  10:                                       Caracteristicas = FlagEnums.Option2 | FlagEnums.Option5,
  11:                                       //Caracteristicas = 00000010        | 00001000           = 00001010
  12:                                       Nombre = "Cliente 1",
  13:                                       VolumenNegocio = 10.0m
  14:                                   };
  15:  
  16:             //comprobamos q realmente tiene FlagEnums.Option5 
  17:             if ((cliente.HasFlag(FlagEnums.Option5)))
  18:                 Console.WriteLine(cliente.HasFlagVerbose(FlagEnums.Option5));
  19:  
  20:             //comprobamos q realmente NO tiene FlagEnums.Option4
  21:             if (!(cliente.HasFlag(FlagEnums.Option4)))
  22:                 Console.WriteLine(cliente.HasFlagVerbose(FlagEnums.Option4));
  23:  
  24:             //quitamos FlagsEnums.Option5
  25:             cliente.Caracteristicas = cliente.Caracteristicas ^ FlagEnums.Option5;
  26:  
  27:             //comprobamos q hemos quitado FlagEnums.Option5
  28:             if (!(cliente.HasFlag(FlagEnums.Option5)))
  29:                 Console.WriteLine(cliente.HasFlagVerbose(FlagEnums.Option5));
  30:  
  31:             Console.ReadKey();
  32:         }
  33:     }

 

A partir de aquí… posibilidades infinitas ;-))

Más info:

Actualizando HTC Dream de Movistar a Cupcake (Android 1.5)

Pues ejerciendo el falso y sobrevalorado “derecho” de Canje de Puntos después de contribuir durante más dos años a aumentar las arcas de Telefónica para que, cada vez más vayan subsituyendo mano de obra en los departamentos de atención telefónica por maquinaria automática absurda, adquirí el viernes, y no voy a entrar a explicaros todos los pasos que he realizado para conseguirlo, pues empezaría a blasfemar, un HTC Dream tras pagar 239pavos e hipotecar mi futuro dos años con Movistar.

La decisión de compra se debió a que mi flamante HTC HD con Windows Mobile tuvo un pequeño percance. Mi relación con el soporte técnico de HTC no lo detallaré pues convertiría este post en un manifiesto anti-multinacionales tecnológicas.

La cuestión es que tras empezar a utilizar el HTC Dream de Movistar y familiarizarme un poco con Android (gracias al  Centro de Soporte Telefónico del Maestro Alejandro Mezcua 😉 ) me cercioré de que la versión de Android que suministra este terminal, además de estar basado en Android 1.1, está algo capada. Por ejemplo, no puedo sincronizar contactos. Manda huevos. Buscando, encontré en http://and.roid.es un post de un tal Rick el cual explicaba paso por paso la actualización del HTC Dream de Movistar a Cupcake con lo que me puse manos a la obra.

Escribo este post para animar a los que no están muy seguros de si actualizar o no. En comparación, tras instalar Android 1.5, encuentro, además de la posibilidad de sincronización de Agenda, Contactos y Correo (menos mal), muchas otras aplicaciones que no vienen de serie, como por ejemplo uso de la Videocámara, además de algún Widget que otro. Además en el propio proceso de actualización se actualiza la radio (firmware), lo cual sí he notado, especialmente en cobertura.

Actualización

Seguid los pasos tal y como indica Rick en su post. Únicamente hay una error en la combinación de teclas que enumera para reiniciar el dispositivo en Fastboot, que es Home+Back en lugar de alt + back. Aseguraros de descargaros todos los componentes antes de todo. La guía hace referencia al SDK del Android 1.5 r1 y la última es la r3, pero es trivial.

Recomendable descargarse el FastBoot desde la Web de HTC e instalarla en un directorio de la raíz, al poder ser dentro de un subdirectorio del SDK pues se tiene que ejecutar desde la línea de comandos.  Tras reiniciar el dispositivo en modo FastBoot no te resultará muy difícil seguir las instrucciones pues además de estar en castellano las opciones son claras. Si no quieres reiniciar el dispositivo en varias ocasiones y debido a que tienes que utilizar la SD para copiar los archivos update.zip (del cupcake y del firmware) no estaría de más tener un lector de tarjetas microSD he ir copiando y/o renombrando archivos.

No olvidéis actualizar el firmware. Yo me equivoqué y descargué y renombre un archivo zip de recursos el cual tenia la radio, con lo que tras ejecutarlo con Fastboot me dio un error; únicamente hay que comprimir en update.zip la carpeta radio.

Tras la actualización del cupcake y de la radio, no tuve que hacer nada más si utilizas la ROM de Ricky. El teclado funcionan perfectamente y la conexión APN de movistar viene configurada.

SQL Server –> Sync <- SQL Azure = Proyecto Huron muestra sus cartas!

Ayer se presentó en el SQL Pass Conference, en Seattle, las primeras imágenes del proyecto “Huron” el cual permitirá la sincronización de orígenes de datos SQL Server con SQL Azure.

En breve, Microsoft pondrá a disposición del público la primera CTP así como un add-in para SQL Server Management Studio. La idea es proporcionar un Wizard para la creación de un contexto de sincronización para que posteriormente SQL Agent sea el encargado de ejecutar las sincronización de ambos proveedores.

Probablemente el próximo PDC hayan más noticias.

Aquí van una imágenes.