.NETで全角／半角・大文字／小文字を考慮した文字列比較

掲題の通りですが、コードの書き方はあれど結果をまとめてあるものが見当たらず毎回コードを動かして確認しているので、備忘録としてまとめます。直感と異なる挙動もあり覚えるのも容易ではありません。
環境：.NET ver 6.0.300

# 3行で
* 大文字小文字を区別する/しないなら`StringComparison`を受け取るオーバーロードに明示的に指定
* 全角半角の区別する/しないは癖が強く挙動に一貫性がないので注意が必要
* .NETのバージョンによって挙動が異なりそうなので要実運用環境テスト

# 参考
* [文字列を比較する方法](https://learn.microsoft.com/ja-jp/dotnet/csharp/how-to/compare-strings)
* [.NET での文字列の比較に関するベスト プラクティス](https://learn.microsoft.com/ja-jp/dotnet/standard/base-types/best-practices-strings)
* [2つの文字列が等しいかを調べる
大文字小文字、半角全角、ひらがなカタカナの区別をしないで文字列を比較する](https://dobon.net/vb/dotnet/string/stringequals.html)

# `string.Equals`
等しければ`True`、異なれば`False`
* `StringComparison.Ordinal`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**True**|False|False|False|
|"TEST"|False|**True**|False|False|
|"ｔｅｓｔ"|False|False|**True**|False|
|"ＴＥＳＴ"|False|False|False|**True**|
* `StringComparison.OrdinalIgnoreCase`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**True**|**True**|False|False|
|"TEST"|**True**|**True**|False|False|
|"ｔｅｓｔ"|False|False|**True**|**True**|
|"ＴＥＳＴ"|False|False|**True**|**True**|
* `StringComparison.CurrentCulture`(ja-JP)

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**True**|False|**True**|False|
|"TEST"|False|**True**|False|**True**|
|"ｔｅｓｔ"|**True**|False|**True**|False|
|"ＴＥＳＴ"|False|**True**|False|**True**|
* `StringComparison.CurrentCultureIgnoreCase`(ja-JP)

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**True**|**True**|False|False|
|"TEST"|**True**|**True**|False|False|
|"ｔｅｓｔ"|False|False|**True**|**True**|
|"ＴＥＳＴ"|False|False|**True**|**True**|

# `StringComparer.Equals`
`string.Equals`と同様

# `new CultureInfo("ja-JP").CompareInfo.Compare`
等しければ`0`、異なれば並べ替え順序によって負か正の値
* `CompareOptions.None`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**0**|<0|**0**|<0|
|"TEST"|>0|**0**|>0|**0**|
|"ｔｅｓｔ"|**0**|<0|**0**|<0|
|"ＴＥＳＴ"|>0|**0**|>0|**0**|
* `CompareOptions.IgnoreCase`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**0**|**0**|<0|<0|
|"TEST"|**0**|**0**|<0|<0|
|"ｔｅｓｔ"|>0|>0|**0**|**0**|
|"ＴＥＳＴ"|>0|>0|**0**|**0**|
* `CompareOptions.Ordinal`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**0**|>0|<0|<0|
|"TEST"|<0|**0**|<0|<0|
|"ｔｅｓｔ"|>0|>0|**0**|>0|
|"ＴＥＳＴ"|>0|>0|<0|**0**|
* `CompareOptions.OrdinalIgnoreCase`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**0**|**0**|<0|<0|
|"TEST"|**0**|**0**|<0|<0|
|"ｔｅｓｔ"|>0|>0|**0**|**0**|
|"ＴＥＳＴ"|>0|>0|**0**|**0**|
* `CompareOptions.IgnoreWidth`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**0**|<0|**0**|<0|
|"TEST"|>0|**0**|>0|**0**|
|"ｔｅｓｔ"|**0**|<0|**0**|<0|
|"ＴＥＳＴ"|>0|**0**|>0|**0**|
* `CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase`

| | "test" | "TEST" | "ｔｅｓｔ" | "ＴＥＳＴ" |
|:-:|:-:|:-:|:-:|:-:|
|"test"|**0**|**0**|**0**|**0**|
|"TEST"|**0**|**0**|**0**|**0**|
|"ｔｅｓｔ"|**0**|**0**|**0**|**0**|
|"ＴＥＳＴ"|**0**|**0**|**0**|**0**|

# 考察
*  `StringComparison.CurrentCulture`では全角半角を区別していないのに、`StringComparison.CurrentCultureIgnoreCase`では全角半角を区別しているあたり、直感と異なる動きとなっていて注意が必要です。`StringComparison.CurrentCultureIgnoreCase`ではすべて`True`になると思っていました。
* `CompareOptions.None`では全角半角を区別していないのに、`CompareOptions.IgnoreCase`フラグを立てたとたんに全角半角を区別しているあたりもちぐはぐな動きだと思います。
* それ以外は立てたフラグの通りの動作になっているようです。
* [参考先](https://dobon.net/vb/dotnet/string/stringequals.html#section3)では今回と異なる挙動となっていたことが読み取れます。
> 現在のカルチャが日本語（ja-JP）の環境で試した限りでは、どのStringComparison列挙体のメンバを使っても、全角文字と半角文字（'Ａ'と'A'、'ア'と'ｱ'など）を区別しないで比較することはできませんでした。

比較結果は.NETのバージョンとかによるのかもしれません。[.NET での文字列の比較に関するベスト プラクティス](https://learn.microsoft.com/ja-jp/dotnet/standard/base-types/best-practices-strings)でも
> さらに、文字列比較を、異なるバージョンの .NET を使用したり、異なるオペレーティング システムまたはバージョンが異なるオペレーティング システム上の .NET で実行したりすると、異なる結果が返る可能性があります。 詳細については、「[Strings and The Unicode Standard](https://learn.microsoft.com/ja-jp/dotnet/api/system.string#Unicode)」(文字列と Unicode 標準) を参照してください。

とあります。とはいえ今回確認した挙動はUnicodeとかそういう問題じゃない気もしますが…
* InvariantCultureは用途が限定的なので今回はスルーしています
* [`CompareOptions`のフラグ](https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.compareoptions?view=net-7.0)を見るにつけ、日本語は特別扱いされているというか、頭の痛い言語だなと思います。

# まとめ
文字列比較に明示的にオプションを指定するのはプログラマの嗜みの1つですが、それもこう挙動に一貫性でないと苦労します。
仕様をしっかり把握したうえで間違いのないオプションを指定したいですね。
あとテストはちゃんとしましょう。