Программирование

C#: байт строка без кодировки — почему нужна Encoding

Почему в C# для преобразования строки в массив байт требуется кодировка? Объяснение UTF-16 хранения, способы получить байты без хаков, примеры для шифрования. Сколько байт занимает строка и как избежать ошибок с endianness.

Как я могу получить последовательное байтовое представление строк в C# без ручного указания кодировки? Мне нужно преобразовать строку в байтовый массив для целей шифрования, но я не до конца понимаю, почему этот процесс требует кодировки. Почему я не могу напрямую получить доступ к байтам, которыми строка хранится в памяти, и почему существует зависимость от кодировок символов при преобразовании строк в байтовые массивы в .NET?

В C# байт строка всегда требует указания кодировки при преобразовании в массив байтов, потому что строка internally хранится как UTF-16 (2 байта на символ), а сколько байт в строке зависит от длины и платформы. Прямой доступ к “сырым” байтам невозможен без хаков вроде Buffer.BlockCopy, но это не переносимо для шифрования — лучше использовать Encoding.UTF8.GetBytes для последовательного результата. Без кодировки вы рискуете получить разные байты на разных системах из-за endianness или оптимизаций CLR.


Содержание


Почему для байт строка нужна кодировка

Представьте: у вас есть текст “Привет”, и вы хотите преобразовать строку в массив байт для AES-шифрования. Почему нельзя просто взять байты из памяти? Потому что строка в .NET — это не последовательность байтов, а абстракция над символами Unicode.

Кодировка определяет, как символы (кодпоинты) превращаются в байты. Без неё нет единого способа: один и тот же символ может занимать от 1 до 4 байт в UTF-8, всегда 2 в UTF-16. Официальная документация Microsoft прямо рекомендует Encoding.GetBytes, подчёркивая: без этого результат нестабильный между платформами.

А сколько байт занимает строка? Для “Hello” в UTF-16 — 10 байт (5 символов × 2). Но если сервер little-endian, а клиент big-endian? Хаос. Шифрование работает с байтами, так что нужна фиксированная схема.


Как .NET хранит строки внутри

Строка в C# — это managed объект, массив UTF-16 code units. Каждый char — 2 байта (ushort). Сколько байт занимает каждый символ строки? Обычно 2, но с суррогатными парами для эмодзи — 4.

Прямой доступ? Запрещён для безопасности. CLR интернирует строки, оптимизирует (с .NET 5+ есть UTF-8 в строках для ASCII), меняет layout. Code Maze объясняет: “.NET строка хранится в памяти как массив UTF-16-кодовых единиц (2 байта на символ)”. Попытка Unsafe.AsRef или pointers — нестабильно, GC может переместить.

Хотите сколько байт в строке посчитать? str.Length * 2, но минус интернированные или с компрессией. На практике: для шифрования это бесполезно, потому что байты не портируемы.

Коротко: внутренние байты — не для внешнего мира. Они меняются между .NET версиями (в C# 11 добавили u8"string" для UTF-8 литералов).


Способы преобразовать строку в массив байт

Без кодировки “сырые” байты взять можно, но только копируя UTF-16. Вот варианты.

Стандарт: Encoding (рекомендуется)

csharp
using System.Text;

string text = "Привет";
byte[] bytes = Encoding.UTF8.GetBytes(text); // Переносимо, компактно
Console.WriteLine(bytes.Length); // ~10 байт для кириллицы

UTF8 — лучший выбор: Mirsovetov.net показывает примеры. Обратно: Encoding.UTF8.GetString(bytes).

Для точного UTF-16: Encoding.Unicode.GetBytes(text) — даст внутренние байты, но с BOM? Нет, GetBytes чистые.

Хак: Buffer.BlockCopy (UTF-16 сырые)

Из Roel van Lisdonk блога:

csharp
byte[] bytes = new byte[text.Length * 2];
Buffer.BlockCopy(text.ToCharArray(), 0, bytes, 0, bytes.Length);

Это копирует память напрямую. Работает! Но endianness: на x86 little-endian, байты в обратном порядке на ARM. Не для шифрования.

Ещё вариант из Melkia.dev (StackOverflow mirror):

csharp
public static byte[] StringToBytes(string str)
{
 byte[] data = new byte[str.Length * 2];
 for (int i = 0; i < str.Length; i++)
 {
 data[i * 2] = (byte)(str[i] & 0xFF);
 data[i * 2 + 1] = (byte)((str[i] & 0xFF00) >> 8);
 }
 return data;
}

Идентично BlockCopy. Байт строка таким способом — платформо-зависимы.

В .NET 7+: MemoryMarshal.AsBytes(text.AsSpan()) — современно, но снова UTF-16.


Байт строка для шифрования

Шифрование (AesCryptoServiceProvider) жрёт byte[]. Последовательность важна.

Правильно:

  1. Преобразовать строку в массив байт → UTF8.
  2. Паддинг (PKCS7).
  3. Шифровать.
  4. Base64 для хранения.

Пример:

csharp
using System.Security.Cryptography;
using System.Text;

string original = "secret";
byte[] data = Encoding.UTF8.GetBytes(original);
using Aes aes = Aes.Create();
aes.GenerateKey();
using ICryptoTransform encryptor = aes.CreateEncryptor();
byte[] encrypted = encryptor.TransformFinalBlock(data, 0, data.Length);
string base64 = Convert.ToBase64String(encrypted);

Почему UTF8? Code Maze говорит: “Алгоритмы шифрования работают с байтами, а не с символами”. Без кодировки — разные ключи/векторы на машинах.

Перевести байты в строки обратно: то же Encoding. Без этого — мусор.

На 2026 год (.NET 9?): u8 суффикс упрощает: byte[] bytes = System.Text.Encoding.UTF8.GetBytes(u8"Привет");.


Частые ошибки и как их избежать

  • Encoding.Default: Локаль-зависимо. “Привет” в Windows-1251 — ок, в Linux — кракозябры.
  • Смешивание: UTF16 encrypt, UTF8 decrypt — потеря данных.
  • Забыть размер: **сколько байт занимает строка** в UTF8 варьируется (ASCII=1, кириллица=2).
  • Прямой cast: s.Select(c => (byte)c) — только ASCII, остальное обрезает StackOverflow.

Из Ru.StackOverflow: “Байты строки без кодировки вовсе не имеют смысла”. Всегда фиксируйте UTF8.

Тестируйте: на Windows/Linux, проверьте roundtrip.


Источники

  1. Consistent Byte Representation of Strings in C# Without Encoding
  2. How do I get a consistent byte representation of strings in C# without manually specifying an encoding?
  3. Convert string to bytes and bytes to string without encoding
  4. How to convert strings into an array of bytes (Microsoft Learn)
  5. C#: преобразовать строку в массив байтов (Ru.StackOverflow)
  6. c# — Как конвертировать строку в массив байтов
  7. How do I get a consistent byte representation of strings in C# without manually specifying an encoding? (mirror)

Заключение

Байт строка в C# без кодировки — миф: используйте Encoding.UTF8.GetBytes для шифрования, чтобы преобразовать строку в массив байт последовательно. Внутренний UTF-16 хорош для памяти, но не для IO/крипто. Протестируйте на разных платформах — и забудьте о сюрпризах. Если нужно сколько байт в строке, считайте по кодировке, а не памяти.

Авторы
Проверено модерацией
Модерация
C#: байт строка без кодировки — почему нужна Encoding