Hatena::ブログ(Diary)

当面C#と.NETな記録 このページをアンテナに追加 RSSフィード

2009/11/11 (水)

[] あなたがやりたいことはきっと "Hoge".IndexOf( "Hoge" ) ではなく "Hoge".IndexOf( "Hoge", StringComparison.Ordinal )  あなたがやりたいことはきっと "Hoge".IndexOf( "Hoge" ) ではなく "Hoge".IndexOf( "Hoge", StringComparison.Ordinal )を含むブックマーク  あなたがやりたいことはきっと "Hoge".IndexOf( "Hoge" ) ではなく "Hoge".IndexOf( "Hoge", StringComparison.Ordinal )のブックマークコメント

ずいぶん前にも書きましたが string の IndexOf には罠があります。ただ単に IndexOf( "Hoge" ) と書くと IndexOf( "Hoge", StringComparison.CurrentCulture ) の動作をしてしまいます。きっとあなたがやりたいことは IndexOf( "Hoge", StringComparison.Ordinal ) だと思います。

.NET4 で、この文字列の危険な落とし穴を修正しようとしたようですが、結局変更はキャンセルされたようです。BCL Team Blog によると、CTP では StartsWith, EndsWith, IndexOf, LastIndexOf をカルチャー依存から非依存(ordinal)に変更したけど、β1で戻したよとあります。実現していれば大きな影響を与えただけに反対されたのでしょうか?

変更しようとした理由はセキュリティへの懸念で、開発者が気づかずにカルチャー依存の文字列比較を行ってしまうことを防ごうとしたようです。これやばいんじゃね?とタレこんだけど使い方次第だと却下された私としては変更して欲しかったのですが、実現ならずで残念です。でも、BCL チームが変更しようとしたということは、やはり危険だと強く認識してるってことなので、無駄じゃなかったのかなとちょっと報われた気持ちです(私のタレこみがきっかけかどうかはわかりませんが)。

もう一度 IndexOf で遊んでみます。@IT 会議室の @echo さんの例を試してみます。「〇」は漢数字の零です。

using System;

class P
{
  static void Main()
  {
    Console.WriteLine( "AA".IndexOf("〇A") );           // 0
    Console.WriteLine( "AA".IndexOf("〇") );             // 0
    Console.WriteLine( "A〇A".IndexOf("AA") );         // 0
    Console.WriteLine( "〇A〇A".IndexOf("AA") );       // 1
    Console.WriteLine( "〇A〇A".IndexOf("〇A") );       // 1
    Console.WriteLine( "〇A〇A".LastIndexOf( "〇A" ) ); // 3
    Console.WriteLine();

    Console.WriteLine( "AA".IndexOf( "〇A", StringComparison.Ordinal ) );         // -1
    Console.WriteLine( "AA".IndexOf( "〇", StringComparison.Ordinal ) );           // -1
    Console.WriteLine( "A〇A".IndexOf( "AA", StringComparison.Ordinal ) );       // -1
    Console.WriteLine( "〇A〇A".IndexOf( "AA", StringComparison.Ordinal ) );     // -1
    Console.WriteLine( "〇A〇A".IndexOf( "〇A", StringComparison.Ordinal ) );     // 0
    Console.WriteLine( "〇A〇A".LastIndexOf( "〇A", StringComparison.Ordinal ) ); // 2

    Console.ReadKey();
  }
}

右側に書いた結果は Windows7/.NET3.5.1 での実行結果です。@echo さんの当時の実行結果と違いますが、やはりおかしいのは一緒です。どうしてこうなるのかわかりません。Ordinal での比較は OK ですね。@IT 会議室にめーさんが投稿したきっかけはカルチャー依存比較で無限ループするという問題でした。ソースコードは一見正常に見えるので、カルチャー依存がデフォルトなのは、問題が起きるまで気付かないタイプのいやらしい問題を産むということがわかります。怖いですね。

でもまあ FxCop が指摘してくれるので、使うことをおすすめします。

BCL Team 曰く「StringComparison パラメータをとるオーバーロードが存在するときはいつでも、このパラメータをとらないオーバーロードの代わりにそれを使ってください。それは、あなたのコードをより明白で維持するのをより簡単にします。」だそうです。recommend

でも、忘れちゃうんだよね…。やっぱり変えて欲しいんだけどなぁ…。

(追記) Silverlight で試してみると、ややこしいことにカルチャーに依存しない動作がデフォルトでした。つまり、"Hoge".IndexOf( "Hoge" ) が "Hoge".IndexOf( "Hoge", StringComparison.Ordinal ) の動作と同じです。

それはそれでいいんですが(ややこしいけど)、VisualStudio 2010 beta2 で試してみるとカルチャーに依存しない動作をしました…。どーなってるんだ…。

(追記2) aetos さんからのコメントによると、.NET4 Beta2 の IndexOf はカルチャー依存で、〇のおかしな挙動が修正されたとのことです。おそらく .NET4 が Unicode 5.1 準拠になった影響と思いますが詳しいことはわかりません。

aetos さんの濁点のテストを Silverlight でやってみると、やはり Silverlight の IndexOf は Ordinal でした。そして、StringComparison.CurrentCulture を明示した場合は一致と判断されました。

egtraegtra 2009/11/11 10:41 変更が無理なら、いっそStringComparisonを引数に取らないものにObsoleteAttributeでも付けたらどうなのでしょうかという気がしてきますね。

siokoshousiokoshou 2009/11/11 11:09 ですよねー、まったくです。

aetosaetos 2009/11/11 16:22 Beta2 では、上記のコードは CurrentCulture を明示しても Ordinal と同じ結果になります。
"が".IndexOf("か゛") は 0 となり、"が".IndexOf("か゛", StringComparison.Ordinal) は -1 になります。
MSDN にはやっぱりカルチャー依存って書いてあります。
http://msdn.microsoft.com/ja-jp/library/k8b1470s(VS.100).aspx

Ordinalになったのではなく、CurrentCulture のバグが修正されたような気がします。
Win7 なのも関係している?

siokoshousiokoshou 2009/11/11 20:00 おお、なるほどー、そういうことなんですね!
Win7の影響か、もしくは .NET4 が Unicode 5.1 準拠になった影響か、そこらへんっぽいですね。
すっきりしました、どうもありがとうございます(^^)

トラックバック - http://d.hatena.ne.jp/siokoshou/20091111
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 06 | 09 | 11 | 12 |
2007 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 08 | 09 | 10 | 12 |
2009 | 01 | 03 | 04 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 07 |
2011 | 04 | 07 | 10 |
2012 | 04 | 12 |
2013 | 08 |
2014 | 03 | 08 |
2017 | 09 |