byte[]をDictionaryのkeyにしてはいけない！

# ハマった話

```C#
        [Fact]
        public void ArrayKeytest001()
        {
            byte[] arr1 = [0, 1, 2, 3];
            byte[] arr2_1 = [0, 1];
            byte[] arr2_2 = [2, 3];
            byte[] arr2 = [.. arr2_1, .. arr2_2];

            Dictionary<byte[], string> dic = [];
            dic.Add(arr1, "Hoge");
            dic.TryAdd(arr2, "Fuga");    // (1)

            Log.LogInformation("dic={}", dic);
            // dic=[System.Byte[], Hoge], [System.Byte[], Fuga] 
        }
```

UnitTestの形にして表記しましたが、`arr1`と`arr2`は共に`[0, 1, 2, 3]`なので(1)の`TryAdd()`は`false`が返って、`dic`の中身は`{[0, 1, 2, 3], "Fuga"}`だけになるとおもった。

でも実際は`dic`の中には2つの`KeyValuePair`ができてしまった…。

# 理由

`Dictionary`のKeyは`GetHashCode()`の結果で比較されています。
参考：https://learn.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2

> Dictionary<TKey,TValue> では、キーが等しいかどうかを判断するために等価実装が必要です。 comparer パラメーターを受け取るコンストラクターを使用して、IEqualityComparer<T> ジェネリック インターフェイスの実装を指定できます。実装を指定しない場合は、既定のジェネリック等値比較子 EqualityComparer<T>.Default が使用されます。 型 TKeySystem.IEquatable<T> ジェネリック インターフェイスを実装する場合、既定の等値比較子はその実装を使用します。

残念ながら`byte[]`(というか、配列全部)は当然`IEqualityComparer<T>`を実装していないです。
https://learn.microsoft.com/ja-jp/dotnet/api/system.array

どうも`IStructuralEquatable`は実装しているようですが、`IStructuralEquatable`はDictionaryのKeyの同一性の判断には使用されないようです。
結果、`arr1`と`arr2`は中身は同じであっても`Equals()`的には同一ではないと判断されるようです。
(デフォルトだと`Equals()`はインスタンス自体が同じかどうかを比較するっぽいです。しらんけど。)

# 解決方法

`Dictionary`のKeyには`IEqualityComparer<T>`を実装したクラスを指定する必要があります。
若しくは、`Dictionary`のコンストラクト時に`IEqualityComparer<TKey>`を引数に取るコンストラクタを使用し、その中でbyte[]とbyte[]を比較してやる必要があります。


`IEqualityComparer<TKey>`を使う場合は↓のようなクラスを作成します。

```C#
        public class ByteArrayEqualityComparer : IEqualityComparer<byte[]>
        {
            public bool Equals(byte[] x, byte[] y)
            {
                return GetHashCode(x) == GetHashCode(y);
            }

            public int GetHashCode([DisallowNull] byte[] obj)
            {
                HashCode hash = new();
                foreach (byte b in obj)
                {
                    hash.Add(b);
                }
                return hash.ToHashCode();
            }
        }
```

で、`Dictionary`のコンストラクタにセットします。

```C#
        [Fact]
        public void ArrayKeytest002()
        {
            byte[] arr1 = [0, 1, 2, 3];
            byte[] arr2_1 = [0, 1];
            byte[] arr2_2 = [2, 3];
            byte[] arr2 = [.. arr2_1, .. arr2_2];

            Dictionary<byte[], string> dic = new(new ByteArrayEqualityComparer());
            dic.Add(arr1, "Hoge");
            dic.TryAdd(arr2, "Fuga");

            Log.LogInformation("dic={}", dic);      // dic=[System.Byte[], Hoge]  
        }
```

もしくは、`byte[]`をラップして`IEquatable<T>`を実装した↓のようなClassを用意し、

```C#
        public class ByteArrayWrapper(byte[] src) : IEqualityComparer<byte[]>, IEquatable<ByteArrayWrapper>
        {
            public bool Equals(byte[] x, byte[] y)
            {
                return GetHashCode(x) == GetHashCode(y);
            }

            public bool Equals(ByteArrayWrapper other)
            {
                return GetHashCode() == other.GetHashCode();
            }

            public int GetHashCode([DisallowNull] byte[] obj)
            {
                HashCode hash = new();
                foreach (byte b in obj)
                {
                    hash.Add(b);
                }
                return hash.ToHashCode();
            }

            public override int GetHashCode()
            {
                return GetHashCode(src);
            }
        }

```

これを`Dictionary`のKeyにしてやります。

```C#
        [Fact]
        public void ArrayKeytest003()
        {
            byte[] arr1 = [0, 1, 2, 3];
            byte[] arr2_1 = [0, 1];
            byte[] arr2_2 = [2, 3];
            byte[] arr2 = [.. arr2_1, .. arr2_2];

            Dictionary<ByteArrayWrapper, string> dic = [];      // ***
            dic.Add(new ByteArrayWrapper(arr1), "Hoge");        // ***
            dic.TryAdd(new ByteArrayWrapper(arr2), "Fuga");     // ***

            Log.LogInformation("dic={}", dic);      // dic=[System.Byte[], Hoge]  
        }
```

※ ちなみに例示した実装は毎回Hash値を計算するのでちょっと賢くないです。不変なのに。

御後が宜しいようで…。