На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: maxim84_
  
> Работа со строками в разных кодировках в C#
    Часто программисты, работающие с текстом, сталкиваются с так называемой "ошибкой кодировки", приводящей к тому, что текст становится не читаемым из за того, что символы отображаются неверно. В этой статье разберёмся, откуда берутся подобные ошибки при программировании на C# и как их избегать.

    Сначала опубликую несколько правил, которых придерживаюсь, работая с текстом в C#.
    Итак, первое. Необходимо избавиться от всякого ассоциирования char с числом. Если в си это приемлемо, то в C# это ведёт к ошибкам.
    сhar (эквивалент System.Char) представляет символ, абстрагированно от его кода. Точнее кодировка его инкапсулирована и нам до неё дела нет. Мыслить и говорить о чарах следует так: "перечёркнутое D" или "буква Рэ", но не "символ с кодом 0x1234" (хотя можно так:"символ с кодом 0x00D0 в кодировке Unicode", что эквивалентно фразе "перечёркнутое D").

    Второе. Если нам надо получить код символа из char или char из кода символа (байта или нескольких байт, который мы считали из файла или приняли из сети и т.п.) - мы делаем это с помощью какой-либо кодировки (System.Encoding). Если мы преобразуем числа в сhar другими методами (Convert, например) - это считается нарушением инкапсуляции (т.е. "грязным хаком", но иногда это оправдано с точки зрения производительности). Делать это без глубокого понимания - опасно.

    Третье. Где могут возникнуть ошибки в выборе кодировки?
    а) при вводе кодов символов в C# из внешнего источника - это следствие наших ошибочных знаний об источнике кодов символов и кодировки, которую он использует.
    Пример - читаем символы из файла и кодируем и в char с помощью "System.Encoding.ASCII" (что аналогично "System.Encoding.GetEncoding(20127)"). А файл на самом деле - в кодировке Win-1251. Результат - пропали все русские буквы в массиве char (эти две кодировки отличаются тока кодами выше 127, где и задаются все русские буквы).
    В этом случае, просматривая содержимое строки (System.String) в дотнете мы увидим не читаемую строку.
    б) при выводе кодов во внешний мир - следствие ошибочных знание о приёмнике кодов символов и о том, какую кодировку он ожидает.
    Например, мы отправляем строку по сети какому нибудь серверу. Так как отправлять надо не строка, а массив байт - нам сразу надо задуматься, как сервер этот наш массив байт будет обрабатывать? Иначе говоря - какую кодировку он ожидает. Используя эти знания, мы выбираем кодировку для преобразования строки в байты.
    В этом случае в объекте System.String лежит нормальная, читаемая строка, но послы вывода получается строка уже не читаемая.
    в) при соблюдении "первого" и "второго" - при работе с char (и string) на C# кодировка у нас испортиться не сможет.

    Четвёртое. В C# многие средства ввода и вывода информации в байтах имеют методы установки кодировки, чтобы пользователь мог сразу получать и выводить информацию в виде строк и char-ов. Например, Console.OutputEncoding или класс System.IO.StreamWriter (байтовый ввод-вывод потока он заменяет на строковый, применяя заданную кодировку). Если такому StreamWriter-у надо вывести в поток произвольный объект - он его сначала преобразует в строку (.ToString()), строку превратит в байты заданной кодировкой и выведет байты в поток. Так что если какой-то тип принимает для отправки сразу строку - следует искать у него свойство Encoding или что-то похожее, чтобы знать, как эту строку он будет преобразовывать в байты.

    Пятое. Если кодирвка не знает такого кода, который вы ей дали - она вернёт вам символ "?". Не удивляйтесь если что - таково их поведение.

    Шестое, которое моглобы быть и первым. Википедии неплохо описаны разные кодировки и преведены их таблицы. Предлагаю ознакомиться.
    http://ru.wikipedia.org/wiki/Кодовая_страница
    http://ru.wikipedia.org/wiki/Codepage_866

    Теперь - практика.
    Используя полученные здесь знания, разберём такой случай.
    Юзер пытается вывести в консоль псевдографику. Например, символ с кодом 0xD0 (или 208, в десятичной системе), который в кодировке CP866 имеет вид перевёрнутой буквы Пи. Он пишет для этого такой код:
    ExpandedWrap disabled
      Console.OutputEncoding = Encoding.GetEncoding(866);
      Console.Write(Convert.ToChar(208));

    С первого взгляда, всё логично. Но в консоль выдаётся символ "?". Интересно, почему.
    Давайте разбираться, что на самом деле автор делает этими 2 строками.

    1. установил кодировку Codepage_866 на вывод консоли. Что это значит?
    Надо понимать, что консоль - это часть ОС и общение с ней идёт наверняка уж не в виде объектов C# (вроде System.String), а через WinApi, с использованием массивов байт. Класс System.Console - это обёртка для всей этой рутины. И обёртка эта принимает String для вывода в консоль. Как нам подсказывает "Четвёртое" - эта обёртка внутри преобразует строку в байты, испольуя какую-то кодировку. А то, какая эта кодировка, как раз таки задаётся свойством OutputEncoding. Тут автор посчитал что лучше знает, в какой кодировке надо посылать данные консоли и установил свою.
    Лично я понятия не имею, в какой кодировке "чёрненькое окошко" ждёт байты. Полагаю, это зависит от версии и локали операционки и, потому, я бы не стал трогать значение по умолчанию. В моей системе оно изначально установлено на Codepage_866. Так что автор ничего не сделал этой строкой, лишь положил себе маленькие, но очень коварные грабли на тот случай, если вдруг, под какой-нибудь ОС, консоль будет ждать символов в другой кодировке.

    Дальше автор выполняет "Convert.ToChar(208)"
    Что это значит? он получает char в котором записано число 0x00d0. char в текущей версии CLR воспринимается как код кодировки Unicode. В нём это соответствует такой перечёркнутой букве 'D'. А вообще, как говорит нам Второе, это был "грязный хак". Смотрим дальше.
    Дальше это: "Console.Write(<наш char>);"
    Итак, на входе имеем char (в котором у нас "перечёркнутая D"), а в консоль через WinApi пойдут байтики. Для преобразования char в байты нужна кодировка. метод Write консоли будет использовать кодировку, установленную в Console.OutputEncoding, а там установлено Codepage_866. Давайте посмотрим, как она преобразует наше "перечёркнутое D".
    ExpandedWrap disabled
      Byte b = 208;
      Char ch = Convert.ToChar(b); // в ch -  "перечёркнутое D"
      Byte[] chDec = Console.OutputEncoding.GetBytes(new char[] { ch });


    Что видим? один байт с кодом 0x3f в массиве chDec. по таблице CP866 это соответствует вопросику, который консоль и выводит.
    Почему так? Если перечитать "Пятое" и поискать символ "перечёркнутое D" в таблице CP866, становится ясно, что кодировка CP866 просто не знает такого символа, а значит, возвращает "вопросик".

    Как же правильно?
    Символу "перевёрнуто Пи" в юникоде соответствует код 0x2568. Достаточно просто его вывести.
    ExpandedWrap disabled
      Console.Write(Convert.ToChar(0x2568));

    Если кодировка консоли в данной ОС вообще поддерживает псевдографические символы - он будет выведен. Ура.
    Тут возникает вопрос - как узнать юникодовский эквивалент? Ну, либо используются справочные материалы (та же википедия, там в таблицах кодировок часто указываются юникодовые эквиаленты. Вот на этой таблице URL=http://ru.wikipedia.org/wiki/Codepage_866#CP866 - они указаны подписями в ячейках), либо разбираться в том как организован Юникод.

    Теперь допустим, что символы для вывода мы не сами вбиваем в программу, а читаем из файла в коировке CP866
    Тоесть, мы имеем ввод кодов символов из внешнего источника и, как нам говорит Третье - тут надо быть повнимательней, иначе могут возникнуть ошибки с кодировками.
    Вот код:
    ExpandedWrap disabled
      Byte[] bytesFromFile = new byte[] { 208, 0x84 }; // это какие то байты из внешнего источника - не буду приводить здесь чтение байтов из фалйа или сокета.
      Encoding cp866 = Encoding.GetEncoding(866); // кодирвка, которая используется для конвертации байтов в строку.
      String encoded = cp866.GetString(bytesFromFile); // применяем кодироку к байтам, в дебаге можно убедиться, что в encoded - правильное значение.
      // ... здесь идёт какая-то обработка строки ...
      Console.Write(encoded); // выводим в консоль - не забываем, что тут строка преобразуется обратно в байты перед передачей.

    Результат - символы "Перевёрнутое Пи" и "русское Д" в консоли. Опять Ура.
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script execution time: 0,0209 ]   [ 15 queries used ]   [ Generated: 25.04.24, 13:29 GMT ]