Под другим углом зрения следует, что Encoding
классы предназначены для данных в оба конца, но данные, для которых они предназначены для передачи в оба конца, - это char
данные, закодированные в byte
, а не наоборот. Это означает, что в пределах возможностей рассматриваемого Encoding
каждое значение char
имеет соответствующую кодировку в значениях byte
(1 или более), которые снова превращаются в одно и то же значение char
. (Стоит отметить, что не все Encoding
s могут сделать это для всех возможных char
значений - например, Encoding.ASCII
может поддерживать только char
значения в диапазоне [0, 128)
. )
Итак, если вы начинаете с символьных данных и вам нужен способ сохранить или отправить их на носителе, который работает с байтами (например, файл на диске или сетевой поток), Encoding
является отличным способом преобразовать данные char
в данные byte
и затем снова вернуться на другой конец. (Если вы хотите поддерживать все возможные строки, вам нужно использовать одну из Encoding
s на основе Unicode, например Encoding.Unicode
или Encoding.UTF8
.)
Итак, что это значит, если вы начинаете с группы byte
с? Ну, в зависимости от рассматриваемой кодировки, byte
, с которыми вы работаете, могут на самом деле не быть последовательностью, которую Encoding
когда-либо будет выводить. Вам нужно рассматривать Encoding.GetBytes
как операцию кодирования и Encoding.GetChars
/ Encoding.GetString
как операцию декодирования , и поэтому вы начинаете с произвольного массива байтов и пытается декодировать их.
Для аналогии рассмотрим формат файла JPEG для изображений. Это имеет аналогичный тип кодирования и декодирования , где в этом случае декодированные данные - это не string
, а изображение. Итак, если вы берете произвольную строку байтов, каковы шансы, что она может быть декодирована как изображение JPEG? Ответ на это, очевидно, очень, очень тонкий. Скорее всего, ваши байты будут в конечном итоге идти по пути в декодере, который говорит: «Ух ты, я не ожидал, что этот байт последует за этим другим», и он сделает все возможное, чтобы обработать данные при условии что это допустимый файл JPEG, который каким-то образом поврежден.
Точно так же происходит и при преобразовании произвольного массива байтов в строку. Кодировка UTF-8 имеет особые правила о том, как кодируются значения char
128 и выше, и одно из этих правил гласит, что вы когда-нибудь увидите байт, соответствующий битовой комбинации 10xxxxxx
, после байта, который соответствует шаблону, подобному * 1052. *, 1110xxxx
или 11110xxx
, который «вводит» многобайтовую последовательность (несколько byte
s, представляющих один char
). Таким образом, если ваши данные содержат байт, соответствующий шаблону 10xxxxxx
, который не следует за одним из ожидаемых «вводчиков», кодировщик может только предполагать, что данные каким-то образом повреждены. Что оно делает? Он вставляет символ, который говорит: «Что-то пошло не так с закодированными данными. Я старался изо всех сил. Вот где все пошло не так». Люди, которые разработали Unicode, предвидели этот точный сценарий и создали персонажа с таким точным значением: Заменяющий персонаж .
Итак, если вы пытаетесь совершить круговую передачу ваших byte
с в строке char
с, и этот сценарий встречается, фактическое значение оскорбительного byte
теряется, и вместо него заменяет символ вставлен. Когда вы пытаетесь превратить string
обратно в массив byte
, он в конечном итоге кодирует символ замены, а не исходные данные. Исходные данные потеряны.
То, что вы ищете, это отношения кодирования и декодирования, которые работают в другом направлении. Encoding
предназначен для получения char
данных и поиска способа временно сохранить их как byte
данных. Если вы хотите взять данные byte
и найти способ временно сохранить их как данные char
, вам нужна кодировка, разработанная для этой конкретной цели. К счастью, они существуют. В Википедии есть довольно полный список вариантов. : -)
В .NET Framework самым простым и доступным вариантом является кодировка MIME Base-64, доступная через Convert.ToBase64String
и Convert.FromBase64String
.