ものがたり(旧)

atsushieno.hatenablog.com に続く

Unicode文字/文字列のつかいかた

id:siokoshouさんが言及されていたので、ちょっとこの辺の話を。

僕が例に挙げたやつは、別に僕の発見ではなく、CompareInfoについて調べていたときに@IT会議室が引っかかったというだけのことです。それどころか、こちらで書かれていることと内容としては全く同じ話です。

まず、String.IndexOf()やString.Compare()の処理結果は、CultureInfo.CurrentCultureに依存します。Unicodeのcodepointだけを比較するIndexOf()に相当する機能は無いと思います*1CompareInfo.IndexOf()で、Compare()には対応するCompareOrdinal()というのがSystem.Stringにあります。

InvariantCultureを使っても、文字列がcodepointで比較されるようになるわけではありません。InvariantCultureのCompareInfoでも、Compare()やIndexOf()は「やわらかく」(←てきとーな表現)行われます。この場合、IndexOf()は対応するCultureInfoのCompareInfoのIndexOf(string, string, CompareOptions)に該当します。CompareOptionsは_たぶん_Noneが渡されますし、IgnoreXXXのいずれのフラグも立っていない状態なので、例のゼロ文字が無視されるのは納得できないかもしれませんが、これは納得できません(何だそりゃ)。Unicode Normalization (UTR#15)では、\u3007はcompositionの対象とはされていませんし(されても困る)、Unicode Collation Algorithmのデフォルトテーブルでも\u3007は無視される文字としては定義されていないので*2、前回書いたとおり、MicrosoftのMichael Kaplanがいうところの「MS仕様」なのかもしれません。

このような文字列比較を実際に行っているのは、PlatformSDKでいうところのLCMapStringのはずです(MSDNにそう書かれているので)。だから、上記のMS Accessのページで説明されていることと同じ結果になるわけですね*3

ええと、ちなみにこれは別にUCA (UAX#10)に違反しているということではありません。文字列比較はCulture(Info)に依存して、それぞれの文化に特化して行われて良いので、InvariantCultureも含めて、一と一〇が同一視できるというのがMicrosoftの文化だというのであれば、別にそれはUnicode標準に違反しているわけではないのです*4

Compare()やIndexOf()と似たようなCultureInfo依存の機能に、ToLower(), ToUpper()というものがあります。これは、まずCultureInfoに依存しますので、トルコ(tr)やアゼルバイジャン(az)の環境で実行した結果は、その他の全ての国で実行した結果とは微妙に異なりますし、非ASCII文字についても文字変換が行われます。ややこしいことに、ToUpper()やToLower()は、CompareOptions.IgnoreCaseとは無関係です(のようです。具体例を見つけたら追記します)。

この辺の詳しい話は、例のMichael Kaplanのblogが参考になるでしょう。正しい実装のあり方は勉強できないかもしれませんが、MS実装がどうなっているかについては勉強することができると思います。

*1:どなたかご存じでしたら教えてください

*2:少なくともUAX#10では。

*3:って僕は〇以外は確認してないので、細かい違いがあるかもしれませんが。

*4:もちろん、直すべきかどうかとは別の議論ですが。