Cuando se trata de trabajar con datos binarios C/C++ o D se llevan la palma. La cercanía del lenguaje a los punteros y a estructuras con una correspondencia directa de cada campo con un offset en memoria con respecto al inicio de la estructura, facilita mucho el trabajo.

En C# y haciendo uso de la palabra clave reservada unsafe + fixed, podemos hacer uso de punteros e incluso punteros a estructuras siempre y cuando las estructuras contengan únicamente valores primitivos u otras estructuras que cumplan la misma condición. Poder mapear un array de bytes o un puntero a una estructura y poderla modificar insitu sin tener que extraer cada valor y volverlo a insertar luego individualmente, facilita infinitamente el trabajo con estructuras y datos binarios.

Recientemente me he tenido que enfrentar al problema de trabajar con datos binarios que contenían valores Big Endian y en algunos casos mezclados con otros campos Little Endian. ¿Cómo hacer esto de una forma sencilla?

La gente comenta por ahí que usando BitConverter, o en mi caso BitUtils. O incluso algunas personas lo hacen “a manopla” a base de desplazamiento de bits.

Pues bien, yo tengo una alternativa mucho mejor que nos permitirá tener estructuras/campos/variables o parámetros con campos BigEndian y trabajar con ellos de forma completamente transparente. Aunque la utilidad real es en serialización/deserialización y mapeo de memoria con punteros.

Esto lo podemos lograr creando una estructura que contenga un único entero de la longitud deseada, privado y haciendo uso del implicit operator:

Por ejemplo para un entero de 32 bits:

public struct uint_be  
{  
 private uint _InternalValue;  

 public uint NativeValue  
 {  
  set  
  {  
   _InternalValue = MathUtils.ByteSwap(value);  
  }  
  get  
  {  
   return MathUtils.ByteSwap(_InternalValue);  
  }  
 }  

 public static implicit operator uint(uint_be that)  
 {  
  return that.NativeValue;  
 }  

 public static implicit operator uint_be(uint that)  
 {  
  return new uint_be()  
  {  
   NativeValue = that,  
  };  
 }  
}

A partir de ese momento podremos usar el tipo uint_be como si fuese un entero cualquiera. Se hará un casteo automático con el tipo uint. El campo en memoria estará como big endian, mientras que la propiedad y los casts implícitos trabajarán con un entero little endian. Esta estructura solo sirve para plataformas little endian. Convendría detectar el endian de la plataforma actual y hacer un ByteSwap si es un little endian o no hacer nada si ya es big endian.