.NET の文字列比較でカルチャー未指定を検知する
先日の 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
