Как преобразовать массив байтов в шестнадцатеричную строку и обратно в C#?
Всем привет! Я начинающий C# разработчик, и у меня возникла проблема при работе с бинарными данными. Мне нужно сохранить массив байтов (например, хеш SHA256) в текстовый файл, а потом прочитать его обратно. Я понял, что просто записать байты как есть нельзя — получается кракозябра. Нужно преобразовать их в строку шестнадцатеричных символов, а потом при чтении снова превратить эту строку в массив байтов.
Я пробовал сделать это сам, но мой код работает неправильно. Вот что я написал:
byte[] data = { 0x0F, 0xBA, 0x01, 0xFF };
string hex = "";
foreach (byte b in data)
{
hex += b.ToString("X");
}
// Получается "FBA1FF" — без ведущих нулей!
Console.WriteLine(hex);
Когда я пытаюсь прочитать эту строку обратно, я не могу понять, где заканчивается один байт и начинается другой. Например, строка "FBA1FF" может быть интерпретирована по-разному: {0xFB, 0xA1, 0xFF} или {0x0F, 0xBA, 0x1F, 0xF} — это неправильно.
Я слышал про Convert.ToHexString() и Convert.FromHexString() в .NET 5+, но мой проект на .NET Framework 4.7.2, и эти методы недоступны. Как правильно решить эту задачу?
Подскажите, пожалуйста, надёжный способ конвертации байтов в hex-строку и обратно, который работает во всех версиях .NET. Заранее спасибо!
<<Лучший ответ: универсальное решение для .NET Framework и .NET Core
Проблема вашего кода в том, что метод ToString("X") не добавляет ведущий ноль для байтов меньше 0x10 (например, 0x0F превращается в "F", а не "0F"). Из-за этого строка становится неоднозначной при обратном парсинге.
Вот правильное и надёжное решение, которое работает во всех версиях .NET, начиная с .NET Framework 2.0:
1. Преобразование byte[] в hex-строку
public static string ByteArrayToHexString(byte[] bytes)
{
if (bytes == null || bytes.Length == 0)
return string.Empty;
// Используем StringBuilder для эффективной конкатенации
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
{
// "X2" — гарантирует два шестнадцатеричных символа с ведущим нулём
hex.Append(b.ToString("X2"));
}
return hex.ToString();
}
2. Преобразование hex-строки обратно в byte[]
public static byte[] HexStringToByteArray(string hex)
{
if (string.IsNullOrEmpty(hex))
return Array.Empty<byte>();
// Удаляем возможные дефисы или пробелы
hex = hex.Replace("-", "").Replace(" ", "");
// Проверка на чётную длину
if (hex.Length % 2 != 0)
throw new ArgumentException("Hex string must have an even number of characters");
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length; i += 2)
{
// Convert.ToByte с NumberStyles.HexNumber — самый надёжный способ
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
3. Пример использования
byte[] originalData = { 0x0F, 0xBA, 0x01, 0xFF };
string hexString = ByteArrayToHexString(originalData);
Console.WriteLine(hexString); // "0FBA01FF" — правильно!
byte[] restoredData = HexStringToByteArray(hexString);
// restoredData == { 0x0F, 0xBA, 0x01, 0xFF } — полное совпадение
Console.WriteLine(BitConverter.ToString(restoredData)); // "0F-BA-01-FF"
4. Альтернативный метод (более производительный)
Если вам нужна максимальная производительность (например, для больших массивов), используйте прямое копирование символов:
public static string ByteArrayToHexStringFast(byte[] bytes)
{
char[] result = new char[bytes.Length * 2];
int hexCharIndex = 0;
foreach (byte b in bytes)
{
// Старший полубайт
result[hexCharIndex++] = GetHexChar(b >> 4);
// Младший полубайт
result[hexCharIndex++] = GetHexChar(b & 0x0F);
}
return new string(result);
}
private static char GetHexChar(int value)
{
// value от 0 до 15
return (char)(value < 10 ? '0' + value : 'A' + value - 10);
}
5. Важные замечания
- Регистр символов: В методах выше используется верхний регистр (A-F). Если нужен нижний, замените "X2" на "x2" и 'A' на 'a'.
- Обработка ошибок: Всегда проверяйте входные данные на null и корректность формата.
- Совместимость: Если вы используете .NET 5+, просто вызывайте
Convert.ToHexString(bytes)иConvert.FromHexString(hex)— это встроенные методы. - Альтернатива BitConverter: Встроенный
BitConverter.ToString(bytes)возвращает строку с дефисами (например, "0F-BA-01-FF"). Их можно удалить, но это менее эффективно.
Используйте первый метод (со StringBuilder) для обычных задач — он достаточно быстр и легко читается. Для высоконагруженных систем применяйте второй вариант с массивом char.
<<Альтернативный ответ: использование Span<T> и MemoryExtensions (для .NET Core 2.1+)
Если ваш проект использует .NET Core 2.1 или новее (включая .NET 5/6/7/8), вы можете применить более современный подход с Span<T> для минимизации аллокаций памяти:
Преобразование byte[] в hex-строку
public static string ByteArrayToHexStringSpan(byte[] bytes)
{
if (bytes == null || bytes.Length == 0)
return string.Empty;
// Используем перегрузку с Span для избежания создания промежуточных строк
return Convert.ToHexString(bytes.AsSpan());
}
Преобразование hex-строки в byte[]
public static byte[] HexStringToByteArraySpan(string hex)
{
if (string.IsNullOrEmpty(hex))
return Array.Empty<byte>();
// Удаляем возможные дефисы (как от BitConverter)
hex = hex.Replace("-", "");
// Используем встроенный метод с проверкой
return Convert.FromHexString(hex);
}
Преимущества этого подхода:
- Минимальное выделение памяти (работает напрямую с Span).
- Встроенная проверка на корректность входных данных.
- Высокая скорость выполнения.
Недостаток: Методы Convert.ToHexString и Convert.FromHexString доступны только начиная с .NET 5.0. Для .NET Core 2.1–3.1 используйте Span<T> вручную:
public static string ByteArrayToHexStringSpanManual(byte[] bytes)
{
Span<char> chars = stackalloc char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
byte b = bytes[i];
chars[i * 2] = GetHexChar(b >> 4);
chars[i * 2 + 1] = GetHexChar(b & 0x0F);
}
return new string(chars);
}
private static char GetHexChar(int value) =>
(char)(value < 10 ? '0' + value : 'A' + value - 10);
Этот метод особенно эффективен для небольших массивов (до 256 байт), так как использует стек вместо кучи.