KeyedCollection ジェネリック クラス の紹介

最近知ったんだけど、.NET のコレクションクラスの中に、KeyedCollection<TKey, TItem> クラス てのがある。
これが、少し便利だったので紹介してみます。


こいつは名前の通り、要素に Key でアクセス出来る。(インデックスでも可能。)
Key の指定は、要素から特定のルール(GetKeyForItem メソッド を 実装する)で導き出される。
日本語で書いてもなんかうまく伝えられないので、コードを紹介。

class 受注
{
  public int 受注Id { get; set; }
  public int 得意先Id { get; set; }
  public decimal 受注金額 { get; set; }
  public override string ToString()
  {
    return string.Format("受注Id:{0} 得意先Id:{1} 受注金額:{2}", this.受注Id, this.得意先Id, this.受注金額);
  }
}
class 受注Collection : System.Collections.ObjectModel.KeyedCollection
{
  protected override int GetKeyForItem(受注 item)
  {
    return item.受注Id;
  }
}
class Program
{
  static void Main(string[] args)
  {
    受注Collection collection = new 受注Collection();
    受注 item1 = new 受注() { 受注Id = 1, 得意先Id = 500, 受注金額 = 5000m };
    受注 item2 = new 受注() { 受注Id = 2, 得意先Id = 500, 受注金額 = 4200m };
    受注 item3 = new 受注() { 受注Id = 3, 得意先Id = 1000, 受注金額 = 8000m };
    collection.Add(item1);
    collection.Add(item2);
    collection.Add(item3);
    if (collection.Contains(item2.受注Id))
    {
      Console.WriteLine(collection[item2.受注Id]);
    }
  }
}

上のコードを見てもらえば何となく伝わるかなぁ。コレクションに要素を追加する際に、Key を指定してない。なのに、Key でアクセス出来る。
んじゃ Key は何?っとなるんだけど、KeyedCollection.GetKeyForItem メソッドを実装して指定する。

protected override int GetKeyForItem(受注 item)
{
  return item.受注Id;
}

これで、追加した要素にアクセスする場合の Key は追加した 受注クラス の 受注Id プロパティでアクセス出来るようになる。


こいつが便利だと思う利点を何点か挙げてみる。

  • Key で要素にアクセス出来るので無駄な検索コードを書く必要がない。

List で実装すると、Collection に要素が存在するかどうかの判定コードを実装する必要がある。
一応 Contains ていうメソッドがあるんだけど、確かこれは、要素の Equals メソッドで比較するはず。
なので、Equals メソッドを override していないカスタムオブジェクト の場合、System.Object.Equals が呼ばれるので、同一のインスタンスでないと合致しない。
また、Equals メソッドを override したとしても、Equals とは異なる値で比較したい場合には使えない。
(例えば、受注 クラスの Equals は全ての プロパティ が合致する場合 true となる。でも、Collection に存在するかどうかは 受注Id プロパティのみで判断したいって場合。)
この場合は、 Find メソッドを使って存在するかどうかを判断する必要がある。

List<受注> collection = new List<受注>();
受注 item1 = new 受注() { 受注Id = 1, 得意先Id = 500, 受注金額 = 5000m };
受注 item2 = new 受注() { 受注Id = 2, 得意先Id = 500, 受注金額 = 4200m };
受注 item3 = new 受注() { 受注Id = 3, 得意先Id = 1000, 受注金額 = 8000m };
collection.Add(item1);
collection.Add(item2);
collection.Add(item3);
受注 output = collection.Find(m => m.受注Id == item2.受注Id);
if (output != null)
{
  Console.WriteLine(output);
}

毎回、Predicate デリゲート を実装するのは面倒だし、間違えるかもしれない。

  • Add する時に Key を指定しなくて良くなる。

Dictionary で実装すると、Key を間違えて実装しても実行時にエラーが発生しないと分からない。

Dictionary collection = new Dictionary();
受注 item1 = new 受注() { 受注Id = 1, 得意先Id = 500, 受注金額 = 5000m };
受注 item2 = new 受注() { 受注Id = 2, 得意先Id = 500, 受注金額 = 4200m };
受注 item3 = new 受注() { 受注Id = 3, 得意先Id = 1000, 受注金額 = 8000m };
collection.Add(item1.受注Id, item1);
collection.Add(item2.得意先Id, item2); // ここ間違ってるよ!でも、コンパイル時には教えてくれない!!
collection.Add(item3.受注Id, item3);

if (collection.Contains(item2.受注Id))
{
  Console.WriteLine(collection[item2.受注Id]);
}


なんやかんや書いたけど、知らなくても困らないけど、知ってたらより安全に実装出来るよって事。