C#: байт строка без кодировки — почему нужна Encoding
Почему в C# для преобразования строки в массив байт требуется кодировка? Объяснение UTF-16 хранения, способы получить байты без хаков, примеры для шифрования. Сколько байт занимает строка и как избежать ошибок с endianness.
Как я могу получить последовательное байтовое представление строк в C# без ручного указания кодировки? Мне нужно преобразовать строку в байтовый массив для целей шифрования, но я не до конца понимаю, почему этот процесс требует кодировки. Почему я не могу напрямую получить доступ к байтам, которыми строка хранится в памяти, и почему существует зависимость от кодировок символов при преобразовании строк в байтовые массивы в .NET?
В C# байт строка всегда требует указания кодировки при преобразовании в массив байтов, потому что строка internally хранится как UTF-16 (2 байта на символ), а сколько байт в строке зависит от длины и платформы. Прямой доступ к “сырым” байтам невозможен без хаков вроде Buffer.BlockCopy, но это не переносимо для шифрования — лучше использовать Encoding.UTF8.GetBytes для последовательного результата. Без кодировки вы рискуете получить разные байты на разных системах из-за endianness или оптимизаций CLR.
Содержание
- Почему для байт строка нужна кодировка
- Как .NET хранит строки внутри
- Способы преобразовать строку в массив байт
- Байт строка для шифрования
- Частые ошибки и как их избежать
- Источники
- Заключение
Почему для байт строка нужна кодировка
Представьте: у вас есть текст “Привет”, и вы хотите преобразовать строку в массив байт для 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 (рекомендуется)
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 сырые)
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):
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[]. Последовательность важна.
Правильно:
- Преобразовать строку в массив байт → UTF8.
- Паддинг (PKCS7).
- Шифровать.
- Base64 для хранения.
Пример:
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.
Источники
- Consistent Byte Representation of Strings in C# Without Encoding
- How do I get a consistent byte representation of strings in C# without manually specifying an encoding?
- Convert string to bytes and bytes to string without encoding
- How to convert strings into an array of bytes (Microsoft Learn)
- C#: преобразовать строку в массив байтов (Ru.StackOverflow)
- c# — Как конвертировать строку в массив байтов
- 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/крипто. Протестируйте на разных платформах — и забудьте о сюрпризах. Если нужно сколько байт в строке, считайте по кодировке, а не памяти.