C# – Cadena hexadecimal a matriz de bytes

C# – Cadena hexadecimal a matriz de bytes

Este artículo muestra código para convertir una cadena hexadecimal en una matriz de bytes, pruebas unitarias y una comparación de velocidad.

Primero, este diagrama muestra el algoritmo para convertir una cadena hexadecimal en una matriz de bytes.

Para convertir una cadena hexadecimal en una matriz de bytes, debe recorrer la cadena hexadecimal y convertir dos caracteres en un byte a la vez. Esto se debe a que cada carácter hexadecimal representa medio byte.

Cadena hexadecimal a código de matriz de bytes

El siguiente código convierte una cadena hexadecimal en una matriz de bytes. Utiliza un enfoque de búsqueda + desplazamiento de bits.

Comprueba la entrada de cadena hexadecimal en busca de errores, maneja mayúsculas y minúsculas mixtas y omite el "0x" inicial si existe. Creo que es muy importante verificar siempre las condiciones de error y manejar los casos de esquina.

public static class HexUtil
{
	private readonly static Dictionary<char, byte> hexmap = new Dictionary<char, byte>()
	{
		{ 'a', 0xA },{ 'b', 0xB },{ 'c', 0xC },{ 'd', 0xD },
		{ 'e', 0xE },{ 'f', 0xF },{ 'A', 0xA },{ 'B', 0xB },
		{ 'C', 0xC },{ 'D', 0xD },{ 'E', 0xE },{ 'F', 0xF },
		{ '0', 0x0 },{ '1', 0x1 },{ '2', 0x2 },{ '3', 0x3 },
		{ '4', 0x4 },{ '5', 0x5 },{ '6', 0x6 },{ '7', 0x7 },
		{ '8', 0x8 },{ '9', 0x9 }
	};
	public static byte[] ToBytes(this string hex)
	{
		if (string.IsNullOrWhiteSpace(hex))
			throw new ArgumentException("Hex cannot be null/empty/whitespace");

		if (hex.Length % 2 != 0)
			throw new FormatException("Hex must have an even number of characters");

		bool startsWithHexStart = hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase);

		if (startsWithHexStart && hex.Length == 2)
			throw new ArgumentException("There are no characters in the hex string");


		int startIndex = startsWithHexStart ? 2 : 0;

		byte[] bytesArr = new byte[(hex.Length - startIndex) / 2];

		char left;
		char right;

		try 
		{ 
			int x = 0;
			for(int i = startIndex; i < hex.Length; i += 2, x++)
			{
				left = hex[i];
				right = hex[i + 1];
				bytesArr[x] = (byte)((hexmap[left] << 4) | hexmap[right]);
			}
			return bytesArr;
		}
		catch(KeyNotFoundException)
		{
			throw new FormatException("Hex string has non-hex character");
		}
	}
}
Code language: C# (cs)

Pruebas de cadena hexadecimal a matriz de bytes

Estas son las pruebas unitarias que verifican que el código maneja casos de error, maneja diferentes formatos de entrada y convierte correctamente el hexadecimal en una matriz de bytes.

  • utilizando Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass()]
public class HexUtilTests
{
	[DataRow(null)]
	[DataRow("")]
	[DataRow(" ")]
	[DataTestMethod()]
	public void HexToByteArray_WhenNullEmptyOrWhitespace_ThrowsArgumentException(string hex)
	{
		//act & assert
		Assert.ThrowsException<ArgumentException>(() => hex.ToBytes());
	}
	[TestMethod()]
	public void HexToByteArray_WhenOddLength_ThrowsFormatException()
	{
		//arrange
		string hex = "A";

		//act & assert
		Assert.ThrowsException<FormatException>(() =>hex.ToBytes());
	}
	[DataRow("0x")]
	[DataRow("0X")]
	[DataTestMethod()]
	public void HexToByteArray_WhenStartsWithHexStart_AndNoDigitsAfter_ThrowsArgumentException(string hex)
	{
		//act && assert
		Assert.ThrowsException<ArgumentException>(() => hex.ToBytes());
	}
	[TestMethod]
	public void HexToByteArray_WhenHasUpperCaseLetters_ConvertsThemToBytes()
	{
		//arrange
		string hex = "ABCDEF";
		byte[] expected = new byte[]
		{
			0xAB,
			0xCD,
			0xEF
		};

		//act
		var actual = hex.ToBytes();

		//arrange
		CollectionAssert.AreEqual(expected, actual);

	}
	[DataRow("AM")]
	[DataRow("A!")]
	[TestMethod()]
	public void HexToByteArray_WhenHasInvalidHexCharacter_ThrowsFormatException(string hex)
	{
		//act && assert
		Assert.ThrowsException<FormatException>(() => hex.ToBytes());
	}
	[DataRow("0xab")]
	[DataRow("0Xab")]
	[DataRow("ab")]
	[TestMethod()]
	public void HexToByteArray_WhenHasLowercaseHexCharacters_ReturnsByteArray(string hex)
	{
		//arrange
		byte[] expected = new byte[] { 0xAB };

		//act
		var actual = hex.ToBytes();

		//act && assert
		CollectionAssert.AreEqual(expected, actual);
	}
	[DataRow("0xAB")]
	[DataRow("0XAB")]
	[DataRow("AB")]
	[TestMethod()]
	public void HexToByteArray_WhenHasUppercaseHexCharacters_ReturnsByteArray(string hex)
	{
		//arrange
		byte[] expected = new byte[] { 0xAB };

		//act
		var actual = hex.ToBytes();

		//act && assert
		CollectionAssert.AreEqual(expected, actual);
	}
	[DataRow("0x12")]
	[DataRow("0X12")]
	[DataRow("12")]
	[TestMethod()]
	public void HexToByteArray_WhenHasDigits_ReturnsByteArray(string hex)
	{
		//arrange
		byte[] expected = new byte[] { 0x12 };

		//act
		var actual = hex.ToBytes();

		//act && assert
		CollectionAssert.AreEqual(expected, actual);
	}
}
Code language: C# (cs)

Comparación de velocidad:búsqueda/desplazamiento frente a Linq

Comparé este código con un enfoque Linq de una línea (que no tiene manejo de errores).

Generé una cadena hexadecimal aleatoria con mayúsculas y minúsculas, luego ejecuté los dos convertidores 10 veces. Estos son los tiempos promedio en milisegundos para cada tamaño de entrada.

32 caracteres 320 caracteres 3200 caracteres 32.000 caracteres 320.000 caracteres 3.200.000 caracteres
Buscar/cambiar 0,0007 ms 0,013 ms 0,056 ms 0,428 ms 5 ms 41 ms
Linq 0,0043 ms 0,049 ms 0,121 ms 1,173 ms 13,4 ms 103ms

El enfoque de búsqueda/desplazamiento suele ser 2,5 veces más rápido que el enfoque de Linq, incluso con tamaños de entrada más bajos.

Generando cadenas hexadecimales aleatorias

El siguiente código genera una cadena hexadecimal aleatoria con mayúsculas y minúsculas. Usted especifica cuántas iteraciones y el resultado final es una cadena hexadecimal con 32 caracteres para cada iteración. En otras palabras, si especifica 100 000, generará una cadena hexadecimal con 3 200 000 caracteres.

var randomHex = string.Join("", Enumerable.Range(0, 100_000).Select(t =>
{
	var guidHex = Guid.NewGuid().ToString().Replace("-", "");

	return t % 2 == 0 ? guidHex : guidHex.ToUpper();
}));
Code language: C# (cs)