.NETで使用できる読み取り専用の汎用辞書はありますか?


185

読み取り専用プロパティで辞書への参照を返しています。消費者がデータを変更できないようにするにはどうすればよいですか?これがIList私だったら、私は単にそれを返すことができましたAsReadOnly。辞書で私ができる類似のものはありますか?

Private _mydictionary As Dictionary(Of String, String)
Public ReadOnly Property MyDictionary() As Dictionary(Of String, String)
    Get
        Return _mydictionary
    End Get
End Property

4
それを行うためのいくつかの方法が存在しなければならない、または他のIDictionaryにIsReadOnlyプロパティは存在しないだろう...(msdn.microsoft.com/en-us/library/bb338949.aspx
Powerlord

2
不変性の概念上の利点の多くは、ランタイムで強制することなく得ることができます。これが非公開プロジェクトである場合は、規律ある非公式な方法を検討してください。データをコンシューマーに提供する必要がある場合は、ディープコピーを真剣に検討する必要があります。不変コレクションが1)コレクションへの不変参照を必要とする2)シーケンス自体の変更を防止し、3)コレクションのアイテムのプロパティの変更を防止し、これらの一部がリフレクションに違反する可能性があると考える場合、ランタイムの強制は実用的ではありません。
スプレーグ

26
.NET 4.5以降、System.Collections.ObjectModel.ReadOnlyDictionaryがあります^ _ ^
Smartkid '25年

回答:


156

辞書をラップする簡単な実装は次のとおりです。

public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private readonly IDictionary<TKey, TValue> _dictionary;

    public ReadOnlyDictionary()
    {
        _dictionary = new Dictionary<TKey, TValue>();
    }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }

    #region IDictionary<TKey,TValue> Members

    void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
    {
        throw ReadOnlyException();
    }

    public bool ContainsKey(TKey key)
    {
        return _dictionary.ContainsKey(key);
    }

    public ICollection<TKey> Keys
    {
        get { return _dictionary.Keys; }
    }

    bool IDictionary<TKey, TValue>.Remove(TKey key)
    {
        throw ReadOnlyException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dictionary.TryGetValue(key, out value);
    }

    public ICollection<TValue> Values
    {
        get { return _dictionary.Values; }
    }

    public TValue this[TKey key]
    {
        get
        {
            return _dictionary[key];
        }
    }

    TValue IDictionary<TKey, TValue>.this[TKey key]
    {
        get
        {
            return this[key];
        }
        set
        {
            throw ReadOnlyException();
        }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    void ICollection<KeyValuePair<TKey, TValue>>.Clear()
    {
        throw ReadOnlyException();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dictionary.Contains(item);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _dictionary.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return _dictionary.Count; }
    }

    public bool IsReadOnly
    {
        get { return true; }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dictionary.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

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

    #endregion

    private static Exception ReadOnlyException()
    {
        return new NotSupportedException("This dictionary is read-only");
    }
}

11
リンクだけでなく完全なコードを投稿するための+1ですが、興味があります。ReadOnlyDictionaryの空のコンストラクターの意味は何ですか?:-)
サミュエルネフ

20
そのコンストラクタに注意してください。渡されたディクショナリの参照コピーを行う場合、外部のコードが「読み取り専用」ディクショナリを変更する可能性があります。コンストラクタは、引数の完全で深いコピーを行う必要があります。
askheaves、2011

25
@askheaves:良い観察ですが、実際には読み取り専用の型で元の参照を使用することは非常に便利です-プライベート変数に元を保持し、外部のコンシューマーのためにそれを変更します。たとえば、組み込みのReadOnlyObservableCollectionまたはReadOnlyCollectionオブジェクトを確認してください。Thomasは、.Netフレームワークに固有のものとまったく同じように機能するものを提供しました。トーマス、ありがとう!+1
Matt DeKrey、2011年

13
@ user420667:実装方法は「非読み取り専用辞書の読み取り専用ビュー」です。他のいくつかのコードは元の辞書の内容を変更する可能性があり、これらの変更は読み取り専用辞書に反映されます。達成したいことに応じて、それは望ましい動作であるか、そうでない可能性があります...
Thomas Levesque 2012年

6
@Thomas:.NET BCLのReadOnlyCollectionと同じです。変更される可能性のあるコレクションの読み取り専用ビューです。ReadOnlyは不変を意味するものではなく、不変が期待されるべきではありません。
Jeff Yates

229

.NET 4.5

.NET Framework 4.5 BCLはReadOnlyDictionary<TKey, TValue>ソース)を導入しています。

.NET Framework 4.5 BCLにはAsReadOnlyfor辞書が含まれていないため、独自に作成する必要があります(必要な場合)。それは次のようなものになるでしょう。その単純さは、おそらくそれが.NET 4.5の優先事項ではなかった理由をおそらく強調しています。

public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary)
{
    return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}

.NET 4.0以前

.NET 4.5より前のバージョンではDictionary<TKey, TValue>ReadOnlyCollectionListをラップするようなをラップする.NETフレームワーククラスはありません。ただし、作成することは難しくありません。

以下に例を示します。GoogleでReadOnlyDictionaryを使用している場合は他にもたくさんあります。


7
AsReadOnly()いつものDictionary<,>ようにメソッドを作ったことを覚えているようには見えないので、何人が新しいタイプを発見するのでしょうか。ただし、このスタックオーバーフロースレッドは役立ちます。
Jeppe Stig Nielsen 2012

@Jeppe:覚えていることとは関係ないと思います。すべての機能にコストがかかり、AsReadOnlyが優先リストの上位にあったとは思いません。特に、記述が非常に簡単だからです。
ジェフイェーツ

1
これは単なるラッパーであることに注意してください。基になるディクショナリ(コンストラクターに渡されるディクショナリ)を変更しても、読み取り専用ディクショナリは変更されます。stackoverflow.com/questions/139592/…
TrueWill

1
@JeffYatesそれがどれほど単純かを考えると、それを書くことは、それを書くことに時間を費やすかどうかを決めるよりも短い時間で済むでしょう。そのため、私の賭けは「彼らが忘れた」ことになっています。
Dan Bechard

TrueWillが述べたように、基になるディクショナリはまだ変更できます。あなたが真の不変たい場合は、コンストラクタに元の辞書のクローンを渡すことを検討する必要があります(キーと値の型も不変であると仮定します。)
user420667

19

最近発表された .NET 4.5以降、インターフェイスSystem.Collections.Generic.IReadOnlyDictionary<TKey,TValue>が含まれている BUILD会議で。証明はここ(Mono)とここ(Microsoft)です;)

ReadOnlyDictionaryも含まれているかどうかはわかりませんが、少なくともインターフェイスを使用すると、公式の.NET汎用インターフェイスを公開する実装を今すぐ作成することは難しくありません。


5
ReadOnlyDictionary<TKey, TValue>(.NET 4.5) - msdn.microsoft.com/en-us/library/gg712875.aspx
myermian

18

私の簡単なラッパーを自由に使ってください。IDictionaryは実装されていないため、ディクショナリを変更するディクショナリメソッドに対して実行時に例外をスローする必要はありません。変更メソッドはありません。IReadOnlyDictionaryという独自のインターフェイスを作成しました。

public interface IReadOnlyDictionary<TKey, TValue> : IEnumerable
{
    bool ContainsKey(TKey key);
    ICollection<TKey> Keys { get; }
    ICollection<TValue> Values { get; }
    int Count { get; }
    bool TryGetValue(TKey key, out TValue value);
    TValue this[TKey key] { get; }
    bool Contains(KeyValuePair<TKey, TValue> item);
    void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex);
    IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
}

public class ReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> _dictionary;
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }
    public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }
    public ICollection<TKey> Keys { get { return _dictionary.Keys; } }
    public bool TryGetValue(TKey key, out TValue value) { return _dictionary.TryGetValue(key, out value); }
    public ICollection<TValue> Values { get { return _dictionary.Values; } }
    public TValue this[TKey key] { get { return _dictionary[key]; } }
    public bool Contains(KeyValuePair<TKey, TValue> item) { return _dictionary.Contains(item); }
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { _dictionary.CopyTo(array, arrayIndex); }
    public int Count { get { return _dictionary.Count; } }
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return _dictionary.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return _dictionary.GetEnumerator(); }
}

4
IDictionary契約に違反していない場合は+1 。OOPの観点から見ると、IDictionaryから継承する方が正しいと思いますIReadOnlyDictionary
2013年

@Sam同意し、もし戻ることができれば、IDictionary(current IReadOnlyDictionary)とIMutableDictionary(current )を持つことが最善で最も正しいと思いますIDictionary
MasterMastic 2014

1
@MasterMasticそれは奇妙な命題です。不変コレクションがユーザーがデフォルトで期待するものであるという逆の仮定に依存する他の組み込みクラスを思い出しません。
Dan Bechard、2016年

11

IsReadOnly on IDictionary<TKey,TValue>は継承されますICollection<T>(としてIDictionary<TKey,TValue>拡張)。いかなる方法でも使用または実装されていません(実際には、関連するICollection<T>ICollection<KeyValuePair<TKey,TValue>>ICollection<T>メンバー)。

この問題を解決するには、少なくとも3つの方法があります。

  1. IDictionary<TKey, TValue>提案されているように、カスタムの読み取り専用を実装し、内部辞書にラップ/デリゲートします
  2. 値の使用に応じて、ICollection<KeyValuePair<TKey, TValue>>セットを読み取り専用またはとして 返し IEnumerable<KeyValuePair<TKey, TValue>>ます
  3. コピーコンストラクターを使用してディクショナリを複製し、コピー.ctor(IDictionary<TKey, TValue>)を返します。これにより、ユーザーは自由にそれを自由に使用でき、ソースディクショナリをホストしているオブジェクトの状態には影響しません。複製するディクショナリに参照タイプが含まれている場合(例に示すように文字列ではない)、「手動」でコピーを行い、参照タイプも複製する必要があることに注意してください。

余談として; コレクションを公開するときは、可能な限り最小のインターフェイスを公開することを目指します。この例では、型が公開するパブリックコントラクトを壊すことなく、基礎となる実装を変更できるため、IDictionaryである必要があります。


8

読み取り専用ディクショナリはある程度置き換えることができFunc<TKey, TValue>ます。通常、これはAPIで使用します。シンプルで、特に、必要に応じてバックエンドを簡単に置き換えることができます。ただし、キーのリストは提供されません。それが重要かどうかは、あなたが何をしているかに依存します。


4

いいえ、自分でロールするのは簡単です。IDictionaryはIsReadOnlyプロパティを定義します。辞書をラップし、適切なメソッドからNotSupportedExceptionをスローするだけです。


3

BCLでは利用できません。ただし、BCLエクストラプロジェクトでReadOnlyDictionary(ImmutableMapという名前)を公開しました

完全に不変の辞書であることに加えて、IDictionaryを実装し、IDictionaryが取得される任意の場所で使用できるプロキシオブジェクトの生成をサポートします。変化するAPIの1つが呼び出されるたびに例外をスローします

void Example() { 
  var map = ImmutableMap.Create<int,string>();
  map = map.Add(42,"foobar");
  IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map);
}

9
ImmutableMapはバランスの取れたツリーとして実装されます。.NETでは、人々は通常、「ディクショナリ」がハッシュを介して実装されることを期待し、対応する複雑なプロパティを示すため、ImmutableMapを「ディクショナリ」として宣伝することに注意する必要がある場合があります。
Glenn Slayden、2011

code.msdn.comサイトは機能していないようです。BCLextrasがここにgithub.com/scottwis/tiny/tree/master/third-party/BclExtras
BozoJoe

1

辞書の部分的な実装のみを実装し、すべての追加/削除/設定関数を非表示にするクラスを作成できます。

外部クラスがすべての要求を渡す辞書を内部的に使用します。

ただし、ディクショナリは参照型を保持している可能性が高いため、ディクショナリが保持するクラスにユーザーが値を設定できないようにすることはできません(これらのクラス自体が読み取り専用でない限り)。


1

それを行う簡単な方法はないと思います...辞書がカスタムクラスの一部である場合、インデクサーを使用してそれを実現できます。

public class MyClass
{
  private Dictionary<string, string> _myDictionary;

  public string this[string index]
  {
    get { return _myDictionary[index]; }
  }
}

辞書全体とインデクサーを公開できる必要があります。
Rob Sobers

これは非常に良い解決策のようです。ただし、クラスMyClassのクライアントは、ディクショナリの反復処理などのために、ディクショナリの詳細を知る必要がある場合があります。そして、キーが存在しない場合はどうでしょう(TryGetValue()を何らかの形で公開することをお勧めします)。回答とサンプルコードをより完全にすることができますか?
Peter Mortensen

1

+1よくできました、トーマス。ReadOnlyDictionaryをさらに一歩進めました。

多くデールのソリューションのように、私は削除したかったAdd()Clear()Remove()インテリセンスから、など。しかし、私は自分の派生オブジェクトを実装したかったのIDictionary<TKey, TValue>です。

さらに、私は次のコードを壊したいと思います:(再び、デールのソリューションもこれを行います)

ReadOnlyDictionary<int, int> test = new ReadOnlyDictionary<int,int>(new Dictionary<int, int> { { 1, 1} });
test.Add(2, 1);  //CS1061

Add()行の結果:

error CS1061: 'System.Collections.Generic.ReadOnlyDictionary<int,int>' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument 

呼び出し元はそれをIDictionary<TKey, TValue>にキャストできますが、NotSupportedException読み取り専用でないメンバーを使用しようとすると(Thomasのソリューションから)発生します。

とにかく、これもこれを望んでいた人のための私の解決策です:

namespace System.Collections.Generic
{
    public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        const string READ_ONLY_ERROR_MESSAGE = "This dictionary is read-only";

        protected IDictionary<TKey, TValue> _Dictionary;

        public ReadOnlyDictionary()
        {
            _Dictionary = new Dictionary<TKey, TValue>();
        }

        public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        {
            _Dictionary = dictionary;
        }

        public bool ContainsKey(TKey key)
        {
            return _Dictionary.ContainsKey(key);
        }

        public ICollection<TKey> Keys
        {
            get { return _Dictionary.Keys; }
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            return _Dictionary.TryGetValue(key, out value);
        }

        public ICollection<TValue> Values
        {
            get { return _Dictionary.Values; }
        }

        public TValue this[TKey key]
        {
            get { return _Dictionary[key]; }
            set { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); }
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return _Dictionary.Contains(item);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            _Dictionary.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return _Dictionary.Count; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return _Dictionary.GetEnumerator();
        }

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

        void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        bool IDictionary<TKey, TValue>.Remove(TKey key)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }
    }
}


0
public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
    foreach(KeyValuePair<string, string> item in _mydictionary)
        yield return item;
}

2
それとも、行うことができます:public IEnumerable<KeyValuePair<string, string>> MyDictionary() { return _mydictionary; }
パット

0

これは悪い解決策です、下を見てください。

まだ.NET 4.0以前を使用している人のために、私は受け入れられた回答のクラスと同じように機能するクラスを持っていますが、はるかに短いです。既存のDictionaryオブジェクトを拡張し、特定のメンバーをオーバーライド(実際には非表示)して、呼び出されたときに例外をスローさせます。

呼び出し元がAdd、Remove、または組み込みのディクショナリにあるその他の変更操作を呼び出そうとすると、コンパイラはエラーをスローします。これらのコンパイラエラーを発生させるために、Obsolete属性を使用します。このようにして、ディクショナリをこのReadOnlyDictionaryに置き換え、アプリケーションを実行してランタイム例外を待つことなく、問題がどこにあるかを即座に確認できます。

見てください:

public class ReadOnlyException : Exception
{
}

public class ReadOnlyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        : base(dictionary) { }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
        : base(dictionary, comparer) { }

    //The following four constructors don't make sense for a read-only dictionary

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity, IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }


    //Use hiding to override the behavior of the following four members
    public new TValue this[TKey key]
    {
        get { return base[key]; }
        //The lack of a set accessor hides the Dictionary.this[] setter
    }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Add(TKey key, TValue value) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Clear() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new bool Remove(TKey key) { throw new ReadOnlyException(); }
}

このソリューションには、ここに示されている@supercatによって指摘された問題があります。

var dict = new Dictionary<int, string>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
};

var rodict = new ReadOnlyDictionary<int, string>(dict);
var rwdict = rodict as Dictionary<int, string>;
rwdict.Add(4, "four");

foreach (var item in rodict)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Value);
}

このコードは、期待したようなコンパイル時エラーや期待したような実行時例外を発生させるのではなく、エラーなしで実行されます。4つの数字を出力します。これにより、ReadOnlyDictionaryがReadWriteDictionaryになります。


そのアプローチの問題は、そのようなオブジェクトがDictionary<TKey,TValue>、コンパイラの不満なしを期待するメソッドに渡される可能性があり、プレーン辞書タイプへの参照をキャストまたは強制すると、保護が削除されることです。
スーパーキャット2015年

@supercat、がらくた、あなたは正しい。私も良い解決策があると思いました。
user2023861

にチェーンされDictionaryCloneメソッドでの派生物を作ったことを覚えていMemberwiseCloneます。残念ながら、それはバッキングストアをクローニングすることにより効率的にクローン辞書、バッキングストアがあるという事実のために可能である必要がありながらprivateではなく、protected手段、それらのクローンを作成するには、派生クラスのための方法はありません。MemberwiseCloneバッキングストアを複製せずに使用すると、元のディクショナリに加えられた変更によってクローンが壊れ、クローンに加えられた変更によって元の辞書が壊れます。
スーパーキャット2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.