2013年1月8日火曜日

[C#] KeyedCollection を使って拡張可能な Dictionary を作ってみる

お久しぶりです。 今回も .NET Framework のお話です。

XAML 系テクノロジを使って UI を作成していると、 INotifyPropertyChanged や INotifyCollectionChanged インターフェイスの実装が重要になってきます。
これらを実装しないと ViewModel から View へ変更の通知ができませんから。

そこで時々、INotifyCollectionChanged を実装した Dictionary が欲しくなるんです。


通常の Collection であれば、 System.Collections.ObjectModel.ObservableColleciotn<T> というクラスが用意されているのですが、
残念ながら ObservableDictionary<TKey, TValue> なんてクラスは用意されていません。。



しかも、 ObservableColleciotn の元になる Collection クラスは拡張されることを前提に作られているので、 INotifyCollectionChanged を自力で実装しようとしてもそれほど苦労しません。
(protected virtual なメソッド、プロパティがあるので、 override できる)

しかし、 Dictionary は拡張を前提とされていません。

IDictionary を1から実装するのも面倒だし、何かいい方法がないかなあと ずっとモヤモヤしていたのですが、 Twitter を見ていたらちょうどいいクラスを見つけました。


その名も System.Collections.ObjectModel.KeyedCollection<TKey, TItem> !!
Dictionary のように扱える Collection の拡張クラスなんです。
(といっても内部では Dictionary のインスタンスを持ってるんですけどね)


Dictionary と違って TItem 型から TKey 型を取得できないといけないので若干扱いが難しいと思ったのですが、、、
TItem を KeyValuePair<> にしちゃえば TKey 型を取得できるし、 ICollection<KeyValuePair<TKey, TValue>> の形になるので IDictionary の大半を実装したことになるのでは?と気づきました。

そう、KeyedCollection に IDictionary を追加すれば、拡張可能な Dictionary になります!



Dictionary が拡張可能になれば、 INotifyCollectionChanged の追加も簡単に!

ということで早速作ってみました。
作っただけで、細かい動作の確認はしていないので保証はできません。

CollectionBasedDictionary が上述の拡張可能な Dictionary で、
ObservableDictionary は ObservableColleciotn を参考にして CollectionBasedDictionary に INotifyCollectionChanged と INotifyPropertyChanged を追加しました。

ソースは以下のとおり。


2013/11/10 追記


>> 石川様
コメントを投稿することができなかったので、代わりに記事中で返信致します。
コメントありがとうございます!
ChangeItemKey() はキーを変更するだけのメソッドなんですね。
さっそくコードを修正しました!

5 件のコメント:

  1. このコメントは投稿者によって削除されました。

    返信削除
  2. CollectionBasedDictionaryのインデクサが

    168行目 ChangeItemKey(kvp, key);

    だと機能しません。

    Keyが存在しなければ、Add
    Keyが存在するなら、SetItem

    をするようにすれば使えます。

    var kvp = new KeyValuePair(key, value);
    if (base.Contains(key)) {
    var index = base.IndexOf(base.Dictionary[key]);
    this.SetItem(index, kvp);
    } else {
    base.Add(kvp);
    }

    返信削除
  3. はじめまして。
    このコードを私が開発しているオープンソースライブラリ内で使わせて頂いてもよろしいでしょうか。
    ライブラリ:
    https://github.com/gplusnasite/GooglePlusLibrary.NET

    返信削除
    返信
    1. コメントありがとうございます!
      どうぞご自由にお使いください!

      削除
  4. 154行目
    public new TValue this[TKey key]
    このプロパティのset getは現状だとKeyが存在しない場合に例外が発生します。

    以下のように変更すると上手くいきます。
    public new TValue this[TKey key]
    {
    get
    {
    if (!Contains(key))
    {
    throw new KeyNotFoundException("指定されたキーはディクショナリ内に存在しませんでした。");
    }
    return Dictionary[key].Value;
    }
    set
    {
    // KeyValuePair を作成して追加 or 置換する。
    var kvp = new KeyValuePair(key, value);
    if (Contains(key))
    {
    var index = IndexOf(Dictionary[key]);
    SetItem(index, kvp);
    }
    else
    {
    Add(kvp);
    }
    }
    }

    返信削除