Inicio > .NET Framework, C#, Tutorial > Todo lo que siempre quisiste saber sobre colecciones y tenías miedo de preguntar… Banderas y arreglos de bits

Todo lo que siempre quisiste saber sobre colecciones y tenías miedo de preguntar… Banderas y arreglos de bits


Para esta serie de “todo lo que siempre quisiste saber sobre colecciones y tenías miedo de preguntar” me he dado a la tarea de documentar y tratar de explicar las clases que se utilizan para las colecciones, con el propósito de que conozcamos todo lo que el .NET Framework tiene que darnos, en aras de evitar escribir código más estandarizado y robusto. En esta ocasión hablaré de una pequeña clase poco utilizada que bien nos puede salvar de problemas cuando tengamos que trabajar con muchos valores lógicos (booleanos): BitArray.

Esta clase, ubicada en el espacio de nombres System.Collections, nos permite manipular valores booleanos de forma fácil y sencilla. Esto podría no parecer de mucha utilidad, pero hay veces en los que necesitamos crear sistemas que manipulen el estado de un objeto (por ejemplo, una máquina de estados). Cada estado puede ser representado por un valor booleano, o bandera: verdadero o falso, prendido o apagado, etc. Por supuesto, lo primero que viene a la mente es crear una variable de tipo bool para cada bandera. Pero esto puede ser engorroso si tenemos muchas banderas: no solo hace nuestro manejo de banderas algo verboso, sino que hacer operaciones lógicas puede hacernos escribir grandes líneas de código. Es aquí donde BitArray puede sernos de utilidad.

Comencemos por exponer información sobre la clase. Un BitArray, en primer lugar, no permite agregar o añadir elementos: algo contra-intuitivo tratándose de una colección (BitArray implementa ICollection, después de todo). Pero piénsalo de esta forma: un conjunto de estados de un objeto determinado suele estar definido de antemano. En consecuencia, el tamaño del arreglo se determina en el constructor. Veamos algunos constructores.

BitArray bits1 = new BitArray(10);

BitArray bits2 = new BitArray(bits1);

boo[] boolArray = new bool[] { true, true, false, true, false, false, false };
BitArray bits3 = new BitArray(boolArray);

BitArray bits4 = new BitArray(10, true);

byte[] byteArray = new byte[] { 0xff, 0x00 };
BitArray bits5 = new BitArray(byteArray);

La primera llamada nos genera un array con diez bits. La segunda llamada nos genera un array el cual copia el tamaño y los bits contenidos en el primer array. El tercer arreglo se crea con siete bits de la forma 1101000, y la siguiente llamada nos genera un arreglo de diez bits, todos inicializados a 1. Finalmente, la quinta llamada nos genera un array de 16 bits, con los primeros ocho bits establecidos a 1 y los siguientes ocho establecidos a 0.

Como puedes ver, crear un BitArray no es nada complicado. Una vez creado, podemos emplear métodos útiles, de los cuales algunos se implementan de ICollection y otros nos permiten manipular los bits y hacer cálculos sobre ellos. Por ejemplo, Clone nos permite crear un nuevo BitArray copiando los valores del actual, mientras que CopyTo nos permite copiar los valores a un array cualquiera. Get y Set nos permiten leer y escribir un bit en determinada posición, mientras que SetAll establece todos los bits a 1 o 0.

Asimismo, BitArray cuenta con algunos métodos para hacer operaciones lógicas a nivel de bits. And y Or nos permite hacer una comparación lógica conjuntiva y disyuntiva, respectivamente, mientras que Not niega cada uno de los bits. Xor es similar a Or, pero es una disyunción excluyente. Nota que al hacer estas operaciones, se modifica el objeto actual con el resultado. Un ejemplo:

BitArray bits1 = new BitArray(new byte[] { 0xF });
var val = new bool[] { true, true, false, true, true, false, false, true })
BitArray bits2 = new BitArray(val);

bits1.Print();              // 11110000
bits2.Print();              // 11011001
bits1.And(bits2).Print();   // 11010000
bits1.Xor(bits2).Print();   // 00001001
bits1.Or(bits2).Print();    // 11011001
bits1.Not().Print();        // 00100110

El siguiente ejemplo muestra cómo podemos utilizar un BitArray para almacenar el estado de un objeto. Para este caso, creamos una clase llamada Order, que simula una orden de trabajo. Algunos métodos cambian el estado del mismo, por lo que podemos apreciar cómo va evolucionando.

using System;
using System.Collections;
using System.Text;

namespace Fermasmas.Wordpress.Com
{
    class Order
    {
        private BitArray _status;
        private string _number;

        public Order()
        {
            _number = string.Empty;
            _status = new BitArray(8, false);
            _status.Set(0, true);
        }

        public bool IsNew { get { return _status.Get(0); } }
        public bool IsDirty { get { return _status.Get(1); } }
        public bool IsDeleted { get { return _status.Get(2); } }
        public bool IsShipping { get { return _status.Get(3); } }
        public bool IsCanceled { get { return _status.Get(4); } }
        public bool IsComplete { get { return _status.Get(5); } }
        public bool IsError { get { return _status.Get(6); } }
        public bool IsClosed { get { return _status.Get(7); } }

        public override string ToString()
        {
            return new StringBuilder()
                .AppendFormat("New: {0}\n", IsNew)
                .AppendFormat("Dirty: {0}\n", IsDirty)
                .AppendFormat("Deleted: {0}\n", IsDeleted)
                .AppendFormat("Shipping: {0}\n", IsShipping)
                .AppendFormat("Canceled: {0}\n", IsCanceled)
                .AppendFormat("Complete: {0}\n", IsComplete)
                .AppendFormat("Error: {0}\n", IsError)
                .AppendFormat("Closed: {0}\n\n", IsClosed)
                .ToString();
        }

        public string Number
        {
            get { return _number; }
            set
            {
                _number = value;
                _status.Set(1, true);
            }
        }

        public void Delete()
        {
            _status.Set(2, true);
        }

        public void Save()
        {
            _status.Set(1, false);
        }

        public void ShipOrder()
        {
            _status.Set(3, true);
        }

        public void Cancel()
        {
            _status.Set(4, true);
        }

        public void Complete()
        {
            _status.Set(5, true);
        }

        public void ReportError()
        {
            _status.Set(6, true);
        }

        public void Close()
        {
            _status.Set(7, true);
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            Order order = new Order();
            Console.WriteLine(order);

            order.Number = "99901";
            Console.WriteLine(order);
            order.Save();
            order.ShipOrder();
            Console.WriteLine(order);
            order.ReportError();
            order.Cancel();
            order.Close();
            Console.WriteLine(order);

            Console.ReadKey(true);
        }
    }
}

La salida de este programa es la siguiente.

New: True
Dirty: False
Deleted: False
Shipping: False
Canceled: False
Complete: False
Error: False
Closed: False


New: True
Dirty: True
Deleted: False
Shipping: False
Canceled: False
Complete: False
Error: False
Closed: False


New: True
Dirty: False
Deleted: False
Shipping: True
Canceled: False
Complete: False
Error: False
Closed: False


New: True
Dirty: False
Deleted: False
Shipping: True
Canceled: True
Complete: False
Error: True
Closed: True

Como puedes ver, BitArray es una clase útil de conocer, aunque concedido, no siempre se utiliza. Pero también puedes ver que cuando trabajamos con muchos estados puede ser una forma más eficiente de solucionar el problema.

Eso es todo por el momento. Noi ci vediamo!

ADDENDUM

BitArray tiene una clase muy similar, hermana diría yo: BitVector32, ubicada en el espacio de nombres System.Collections.Specialized. Al igual que BitArray, esta clase se utiliza para guardar valores booleanos (banderas), pero con ciertas diferencias.

En primer lugar, BitVector32 es una estructura, y por tanto, un tipo-valor. Esto quiere decir que si yo asigno una instancia a una variable, dicha instancia será copiada. Y en segundo lugar, BitVector32 tiene un tamaño fijo: 32 bits. Esto la hace más eficiente en ciertos escenarios, dado que BitArray mantiene estados para hacer crecer el tamaño de bits que utiliza.

BitVector32 cuenta, además, con un método (estático) utilísimo cuando tratamos con banderas: CreateMask. Este método genera máscaras de bits en base a una máscara anterior. Por ejemplo, CreateMask() regresa un BitVector con la máscara 00000001. Si llamo otra vez a CreateMask, pasándole como parámetro la primera máscara, me regresa 00000010. Una nueva llamada en forma similar, regresaría 00000100. Y así sucesivamente.

No dejes de darle una revisada a la documentación en MSDN.

Categorías:.NET Framework, C#, Tutorial Etiquetas: ,
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s