2

私は辞書として機能する C# クラスを持っているので、現在 IDictionary をサポートしています。

プロパティ Keys と Values を除いて、すべて問題ありません。

ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }

内部にキーまたは値のコレクションを持っていないので、これらを ICollection として提供する方法を考えています。

私の最初の試みは、次のような「利回り」の魔法を使用することでした。

ICollection<TValue> Values { 
    get {
        for( int i = 0; i < nbValues; ++i ) {
            yield return GetValue(i);
        }
    }
}

しかしもちろん、返される型は IEnumerator ではなく ICollection であるため、これは機能しません...

これが最も簡単な解決策だったので、残念です!

私の 2 番目の試みは、新しく作成した配列に値をコピーし、その配列を返すことでした。

ICollection<TValue> Values { 
    get {
        TValue[] copy = new TValue[nbValues];
        for( int i = 0; i < nbValues; ++i ) {
            copy[i] = GetValue(i);
        }
        return copy;
    }
}

Array は ICollection をサポートしているため、これは機能します。
しかし問題は、ICollection にエントリを追加および削除するメソッドがあることです。呼び出し元がこれらのメソッドを呼び出すと、辞書ではなくコピーのみが変更されます...

私が選択した最終的な解決策は、キーと値のプロパティからこれらのコレクションを返すことができるように、辞書で IDictionary だけでなく ICollection と ICollection もサポートすることです...

public class MyDictionary : IDictionary<TKey,TValue>, 
                            ICollection<TKey>, 
                            ICollection<TValue>
{
}

そのため、プロパティ Keys および Values の get アクセサーは単に「これ」、つまり辞書を返します。

ICollection<TValue> Values { 
    get {
        return this;
    }
}

これはおそらく最も最適なソリューションですが、IDictionary を実装するたびに 2 つの追加のインターフェイスを実装する必要があるのは面倒です。

他にアイデアはありますか?

結局のところ、コピーを配列として返すことはそれほど悪い考えではなかったと思います。とにかく、 IDictionary には、使用する方が理にかなっている Add および Remove メソッドが既にあります。

返されたコレクションを変更しようとすると失敗するため、配列をラップする ReadOnlyCollection を返す方がよいでしょうか?

ICollection<TValue> Values { 
    get {
        TValue[] copy = new TValue[nbValues];
        for( int i = 0; i < nbValues; ++i ) {
            copy[i] = GetValue(i);
        }
        return new System.Collections.ObjectModel.ReadOnlyCollection<TValue>(copy);
    }
}
4

3 に答える 3

2

とにかく、辞書からキーと値を削除できるとは個人的には期待していません。そうしなくても問題Keysないと思います。Values

aを返すことReadOnlyCollection<T>は問題ありません。そうすれば、呼び出し元は、コレクションを変更しようとすると、黙って無視されるのではなく、例外を受け取るだけです。

Dictionary<TKey, TValue>ちなみに、その例外はの動作に従います。

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        IDictionary<string, string> dictionary = 
            new Dictionary<string, string> {{ "a", "b" }};
        dictionary.Keys.Clear();
        Console.WriteLine(dictionary.Count);
    }
}

結果:

Unhandled Exception: System.NotSupportedException: Mutating a key collection
derived from a dictionary is not allowed.
   at System.Collections.Generic.Dictionary`2
            .KeyCollection.System.Collections.Generic.ICollection<TKey>.Clear()
   at Test.Main()

SLaksが言うように、怠惰な独自の実装を作成できる場合はICollection<T>、それが良いでしょう-しかし、それが何らかの理由でトリッキーである場合、または実際にパフォーマンスが重要でない場合は、配列を作成してラップするだけですReadOnlyCollection<T>結構です。ただし、いずれかの方法で期待されるパフォーマンスを文書化することを検討する必要があります。

独自のレイジー実装を作成する場合注意が必要です。基になるデータが変更された場合に返されるコレクションを無効にするために、おそらく何らかの「バージョン番号」が必要です。

于 2013-02-07T16:26:08.370 に答える
1

AReadOnlyCollectionは、リストしたオプションの最良のアプローチです。これらのコレクションは書き込み可能ではありません。

ただし、ゲッターはO(n)であり、これは良くありません。

正しいアプローチはICollection<T>、ディクショナリのライブビューを実装して返す独自のコレクションクラスを作成することです。(そしてミューテーションメソッドからの例外をスローします)

Dictionary<TKey, TValue>これは、 ;が採用したアプローチです。プロパティゲッターが高速であり、余分なメモリを浪費しないことを保証します。

于 2013-02-07T16:25:01.283 に答える
0

回答ありがとうございます。

そのため、Keys プロパティと Values プロパティによって要求された 2 つの ICollection を実装する 2 つのユーティリティ クラスを作成することになりました。IDictionary のサポートを追加する必要がある辞書がいくつかあるので、それらを数回再利用します。

キーのコレクションのクラスは次のとおりです。

public class ReadOnlyKeyCollectionFromDictionary< TDictionary, TKey, TValue >
                       : ICollection<TKey>
                       where TDictionary : IDictionary<TKey,TValue>, IEnumerable<TKey>
{
    IDictionary<TKey, TValue> dictionary;

    public ReadOnlyKeyCollectionFromDictionary(TDictionary inDictionary)
    {
        dictionary = inDictionary;
    }

    public bool IsReadOnly {
        get { return true; }
    }

    Here I implement ICollection<TKey> by simply calling the corresponding method on 
    the member "dictionary" but I throw a NotSupportedException for the methods Add,
    Remove and Clear

    public IEnumerator<TKey> GetEnumerator()
    {
        return (dictionary as IEnumerable<TKey>).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (dictionary as IEnumerable).GetEnumerator();
    }
}

値のコレクションのクラスは次のとおりです。

public class ReadOnlyValueCollectionFromDictionary<TDictionary, TKey, TValue> 
                       : ICollection<TValue>
                       where TDictionary : IDictionary<TKey, TValue>, IEnumerable<TValue>
{
    IDictionary<TKey, TValue> dictionary;

    public ReadOnlyValueCollectionFromDictionary(TDictionary inDictionary)
    {
        dictionary = inDictionary;
    }

    public bool IsReadOnly {
        get { return true; }
    }

    Here I implement ICollection<TValue> by simply calling the corresponding method on 
    the member "dictionary" but I throw a NotSupportedException for the methods Add,
    Remove and Clear

    // I tried to support this one but I cannot compare a TValue with another TValue
    // by using == since the compiler doesn't know if TValue is a struct or a class etc
    // So either I add a generic constraint to only support classes (or ?) or I simply
    // don't support this method since it's ackward in a dictionary anyway to search by
    // value.  Users can still do it themselves if they insist.
    bool IEnumerable<TValue>.Contains(TValue value)
    {
        throw new System.NotSupportedException("A dictionary is not well suited to search by values");
    }

    public IEnumerator<TValue> GetEnumerator()
    {
        return (dictionary as IEnumerable<TValue>).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (dictionary as IEnumerable).GetEnumerator();
    }
}

次に、辞書が TKey と TValue の IEnumerable をサポートしている場合、すべてが非常に簡単になります。

public class MyDictionary : IDictionary<SomeKey,SomeValue>, 
                            IEnumerable<SomeKey>, 
                            IEnumerable<SomeValue>
{
    IEnumerator<SomeKey> IEnumerable<SomeKey>.GetEnumerator()
    {
        for ( int i = 0; i < nbElements; ++i )
        {
            yield return GetKeyAt(i);
        }
    }

    IEnumerator<SomeValue> IEnumerable<SomeValue>.GetEnumerator()
    {
        for ( int i = 0; i < nbElements; ++i )
        {
            yield return GetValueAt(i);
        }
    }

    // IEnumerator IEnumerable.GetEnumerator() is already implemented in the dictionary

    public ICollection<SomeKey> Keys
    {
        get
        {
            return new ReadOnlyKeyCollectionFromDictionary< MyDictionary, SomeKey, SomeValue>(this);
        }
    }

    public ICollection<Value> Values
    {
        get
        {
            return new ReadOnlyValueCollectionFromDictionary< MyDictionary, SomeKey, SomeValue >(this);
        }
    }
}

IDictionary が Keys および Values プロパティの ICollection ではなく IEnumerable を返さないのは残念です。これはすべてとても簡単だったでしょう!

于 2013-02-07T19:33:24.583 に答える