先日の C# 配信で、 「これはブログに書いておくと助かる人がいるんじゃないか」と言われたものをブログ化。
背景: カルチャー依存問題再び
うちのブログでも何回か書いてるんですが、 .NET の文字列比較は、カルチャー依存比較するものと Ordinal (文字コード通り)比較するものが混在していて、なかなかにやばいです。
using static System.Console; // 正規化すると同じ文字になる、文字コード的には別の文字。 var s1 = "a\u0301"; // á = a + ́ var s2 = "\u00e1"; // á // これは false。Ordinal 比較。 WriteLine(new Dictionary<string, int> { { s1, 0 } }.ContainsKey(s2)); // これは true。CurrentCulture 比較。 WriteLine(new SortedDictionary<string, int> { { s1, 0 } }.ContainsKey(s2));
なんでこんなことになるかというと、
-
Dictionary
はEqualityComparer
依存EqualityComparer
はOrdinal
比較Ordinal
比較だと、文字コード的に別の文字は一致しない
-
SortedDictionary
はComparer
依存Comparer
はCurrentCulture
比較CurrentCulture
比較だと、たいていのカルチャーでa\u0301
と\u00e1
を同一視
という仕組み。
やばい。
前述のブログで一応の解決策として、 InvariantGlobalization モード指定してしまうという案も書いたんですが、 このモード変更は影響範囲が結構大きいので、 保守しているコードベースがでかいとなかなか踏み切れない方も多いと思います。
コード解析
このカルチャー依存文字列比較問題は .NET の中の人も把握していて、 .NET 5 の頃にいろいろと対策をしました。 その対策の1つに、NetAnalyzers の提供があります。
NetAnalyzers は要するに、「.NET SDK 付属の公式コード解析」です。 例えば Visual Studio からなら、Dependencis → Analyzers → Microsoft.CodeAnalysis.NetAnalysers のところで確認できます。
この中で、カルチャー依存 API 対策になっているのは以下の項目。
- CA1304: Specify CultureInfo
- CA1305: Specify IFormatProvider
- CA1307: Specify StringComparison for clarity
- CA1310: Specify StringComparison for correctness
こいつら、デフォルトでは Silent なんですよね… (Silent = 何も表示しない。エラーや警告はおろか、サジェストのアイコンすら出ない。)
カルチャー依存 API のやばさのわりに Silent。 まあ、 .NET Framework 1.0 から .NET 5 までの十数年、ずっとそうでしたからね…
ということで、このコード解析の警告・エラー レベルを上げてしまった方がいいかもしれません。 .editorconfig に以下のような行を足せばエラーにできます。
[*.cs] dotnet_diagnostic.CA1304.severity = error dotnet_diagnostic.CA1305.severity = error dotnet_diagnostic.CA1307.severity = error dotnet_diagnostic.CA1310.severity = error
例えば以下のようなメソッドを警告にできます。
using System.Resources; static string? M(ResourceManager m) => m.GetString(""); // CA1304 DateTime.Now.ToString(); // CA1305 "".IndexOf(' '); // CA1307 "abc".StartsWith("abc"); // CA1310