суббота, 10 июля 2010 г.

Путаница в сравнениях: INVARIANT против ORDINAL

Это перевод Comparison confusion: INVARIANT vs. ORDINAL. Автор: Майкл Каплан.

Существует большая путаница между значением этих двух различных вещей в .NET Framework (и когда что использовать). Если вы страдали, страдаете или вы думаете, что будете страдать от этого в будущем - то читайте дальше!

(В противном случае я думаю, что вы можете уйти и вернуться в другое время)

Инвариантная культура является прямым наследником инвариантной локали. Официально добавленная в исходники Windows в 10:23 утра 12-го мая 2001-го, её целью было не использование её в качестве локали (что объясняет, почему для неё не было добавлено никаких данных целый месяц; потому что за это время никто не использовал её в GetLocaleInfo!).

Изначально, у LOCALE_INVARIANT была всего одна благородная цель - позволить использовать CompareStringLCMapString с флагом LCMAP_SORTKEY) так, чтобы она использовала только таблицу весов по-умолчанию в Windows, немного упомянутую тут, но особенно тут. В результате, как указано по второй ссылке, она не изменяется, когда меняется локаль пользователя или системы.

Как я уже сказал, данные для локали были добавлены месяцем позже её создания - по очевидным причинам: если у вас есть LCID, который одна функция считает допустимой, то у вас должна быть действительно хорошая причина, чтобы какая-то другая функция не рассматривала этот LCID как корректный. И эта локаль не должна была быть копией любой другой локали. Для локали добавили странные данные, чтобы никто не пытался притвориться, что он говорит на языке, называемом "инвариантный".

Заметьте, что эти строковые сравнения всё равно имели при этом много лингвистического смысла - потому что половина локалей в Windows используют таблицу весов по-умолчанию, так что инвариантная сортировка не просто производит стабильные результаты, но также и выглядит корректной во многих местах мира.

В .NET Framework были похожие требования (с дополнительной потребностью для поддержки инвариантного парсинга/форматирования) и поэтому было создано свойство CultureInfo.InvariantCulture. Что касается локали, то любые сравнения строк, сделанные с объектом CompareInfo от InvariantCulture, имели бы лингвистический смысл во многих местах, и при этом не изменялись бы в рамках установки .NET Framework.

Так что все получили то, что хотели, да?

Ну, вообще-то нет.

Куча людей хотели метод, который производил бы "более бинарное сравнение", вместо сравнения, основанного на подходе "лингвистического смысла".

Одно из преимуществ такого подхода в скорости сортировки, поскольку для сортировки используется простое сравнение значений кодовых точек.

Чтобы удовлетворить эти ожидания, была добавлена последовательная сортировка (ordinal sort), а в перечисление CompareOptions был добавлен элемент Ordinal. Выбирая его, вы игнорируете любые языковые фишки, получая последовательную двоичную сортировку, которая, соответственно, тоже постоянна, как и инвариантная.

К этому моменту остаётся только проблема, что у вас теперь есть два рабочих способа делать такие инвариантные сравнения, но ни одно из названий не вскакивает в головах разработчиков, когда они начинают искать решения подобных проблем.

Эта проблема актуальна и сегодня, хотя каждый раз, когда я выступаю на конференциях или отвечаю на вопрос в newsgroup или указываю кому-то прочитать пост вроде этого, то в мире становится как минимум на одного разработчика меньше, озабоченного этой проблемой. Может быть, в этот раз этот разработчик - вы? :-)

Но история на этом не заканчивается; многие люди хотят делать вещи без учёта регистра. Конечно же, если вы хотите делать инвариантное сравнение, не чувствительное к регистру, то нет проблем - просто используете флаг CompareOptions.IgnoreCase. Легко!

Но некоторые люди хотели иметь последовательную двоичную сортировку, нечувствительную к регистру?!?

В этот момент лингвист внутри меня содрогается от ужаса, поскольку операции с регистром, очевидно, имеют лингвистическую природу, а последовательная двоичная сортировка намеренно откидывает лингвистичность.

Так что люди просили у нас лингвистическую нелингвистическую сортировку.

Однако, техническая часть меня понимает природу этого, так что мне приходится переступить через свой лингвистический фетиш, как и одному моему коллеге из команды BCL, работающей над Whidbey, который и добавил новый элемент OrdinalIgnoreCase в перечисление CompareOptions.

Поведение для этой опции: сделать сначала операции с регистром, используя casing-таблицу по-умолчанию, а затем сделать обычную двоичную сортировку.

Надеюсь, этот пост помог разложить вещи по полочкам.

This post brought to you by "Ω" (U+03a9, GREEK CAPITAL LETTER OMEGA)

Я говорил с Omega перед тем как опубликовать этот пост. Она сказала, что как последняя буква в греческом алфавите, она понимает важность введения порядка в буквах. И она считает, что выигрыш в производительности - это хорошо. Особенно, когда это позволит ей хоть один раз встать перед её младшей сестрой (U+03c9, GREEK SMALL LETTER OMEGA).

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.