先日の 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));

なんでこんなことになるかというと、

  • DictionaryEqualityComparer 依存
    • EqualityComparerOrdinal 比較
    • Ordinal 比較だと、文字コード的に別の文字は一致しない
  • SortedDictionaryComparer 依存
    • ComparerCurrentCulture 比較
    • CurrentCulture 比較だと、たいていのカルチャーで a\u0301\u00e1 を同一視

という仕組み。

やばい。

前述のブログで一応の解決策として、 InvariantGlobalization モード指定してしまうという案も書いたんですが、 このモード変更は影響範囲が結構大きいので、 保守しているコードベースがでかいとなかなか踏み切れない方も多いと思います。

コード解析

このカルチャー依存文字列比較問題は .NET の中の人も把握していて、 .NET 5 の頃にいろいろと対策をしました。 その対策の1つに、NetAnalyzers の提供があります。

NetAnalyzers は要するに、「.NET SDK 付属の公式コード解析」です。 例えば Visual Studio からなら、Dependencis → Analyzers → Microsoft.CodeAnalysis.NetAnalysers のところで確認できます。

Visual Studio の Solution Exprorer で NetAnalyzers の内容を確認

この中で、カルチャー依存 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