Zwracam odwołanie do słownika w mojej właściwości tylko do odczytu. Jak uniemożliwić konsumentom zmianę moich danych? Gdyby to był IList
, mógłbym go po prostu zwrócić AsReadOnly
. Czy jest coś podobnego, co mogę zrobić ze słownikiem?
Private _mydictionary As Dictionary(Of String, String)
Public ReadOnly Property MyDictionary() As Dictionary(Of String, String)
Get
Return _mydictionary
End Get
End Property
.net
dictionary
readonly
Rob Sobers
źródło
źródło
Odpowiedzi:
Oto prosta implementacja, która otacza słownik:
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"); } }
źródło
.NET 4.5
NET Framework 4.5 BCL wprowadza
ReadOnlyDictionary<TKey, TValue>
( źródło ).Ponieważ .NET Framework 4.5 BCL nie zawiera
AsReadOnly
słowników for, będziesz musiał napisać własny (jeśli chcesz). Byłoby to coś podobnego do poniższego, którego prostota może podkreślać, dlaczego nie był to priorytet dla .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 i starsze
W wersjach starszych niż .NET 4.5 nie ma klasy platformy .NET, która opakowuje element
Dictionary<TKey, TValue>
podobny do ReadOnlyCollection, który zawija listę . Jednak nie jest trudno go stworzyć.Oto przykład - jest wiele innych, jeśli wyszukujesz ReadOnlyDictionary .
źródło
AsReadOnly()
metody na zwykłej metodzieDictionary<,>
, więc zastanawiam się, ile osób odkryje swój nowy typ. Jednak ten wątek przepełnienia stosu pomoże.Podczas niedawnej konferencji BUILD ogłoszono, że od .NET 4.5 interfejs
System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>
jest dołączony. Dowód jest tutaj (Mono) i tutaj (Microsoft);)Nie jestem pewien, czy
ReadOnlyDictionary
jest dołączony, ale przynajmniej z interfejsem nie powinno być trudne stworzenie teraz implementacji, która ujawnia oficjalny ogólny interfejs .NET :)źródło
ReadOnlyDictionary<TKey, TValue>
(.Net 4.5) - msdn.microsoft.com/en-us/library/gg712875.aspxZapraszam do korzystania z mojego prostego opakowania. NIE implementuje IDictionary, więc nie musi generować wyjątków w czasie wykonywania dla metod słownikowych, które zmieniłyby słownik. Metody zmiany po prostu nie istnieją. Zrobiłem dla niego własny interfejs o nazwie 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(); } }
źródło
IDictionary
umowy. Myślę, że z punktu widzenia OOP lepiej jestIDictionary
dziedziczyć zIReadOnlyDictionary
.IDictionary
(dla prąduIReadOnlyDictionary
) iIMutableDictionary
(dla prąduIDictionary
).IsReadOnly on
IDictionary<TKey,TValue>
jest dziedziczony zICollection<T>
(IDictionary<TKey,TValue>
rozszerza sięICollection<T>
jakoICollection<KeyValuePair<TKey,TValue>>
). Nie jest w żaden sposób używany ani implementowany (w rzeczywistości jest „ukryty” poprzez użycie jawnej implementacji skojarzonegoICollection<T>
członków).Istnieją co najmniej 3 sposoby rozwiązania problemu:
IDictionary<TKey, TValue>
i zawiń / deleguj do słownika wewnętrznego, zgodnie z sugestiąICollection<KeyValuePair<TKey, TValue>>
zestaw jako tylko do odczytu lub plikIEnumerable<KeyValuePair<TKey, TValue>>
zależności od użycia wartości.ctor(IDictionary<TKey, TValue>)
i zwróć kopię - w ten sposób użytkownik może robić z nim według własnego uznania i nie ma to wpływu na stan obiektu hostującego słownik źródłowy. Zwróć uwagę, że jeśli słownik, który klonujesz, zawiera typy odwołań (nie ciągi znaków, jak pokazano w przykładzie), będziesz musiał wykonać kopiowanie „ręcznie”, a także sklonować typy odwołań.Tak na marginesie; ujawniając kolekcje, staraj się ujawnić najmniejszy możliwy interfejs - w przykładowym przypadku powinien to być IDictionary, ponieważ umożliwia to różnicowanie podstawowej implementacji bez przerywania kontraktu publicznego, który ujawnia typ.
źródło
Słownik tylko do odczytu można w pewnym stopniu zastąpić
Func<TKey, TValue>
- zwykle używam go w API, jeśli chcę, aby ludzie przeprowadzali wyszukiwania; jest to proste, aw szczególności można łatwo wymienić backend, jeśli kiedykolwiek zechcesz. Nie zawiera jednak listy kluczy; czy to ma znaczenie, zależy od tego, co robisz.źródło
Nie, ale łatwo byłoby zrobić własne. IDictionary definiuje właściwość IsReadOnly. Po prostu zawiń Dictionary i wyrzuć NotSupportedException z odpowiednich metod.
źródło
Brak dostępnych w BCL. Jednak opublikowałem ReadOnlyDictionary (o nazwie ImmutableMap) w moim projekcie BCL Extras
Oprócz tego, że jest w pełni niezmiennym słownikiem, obsługuje tworzenie obiektu proxy, który implementuje IDictionary i może być używany w dowolnym miejscu, w którym jest używany IDictionary. Wyrzuci wyjątek za każdym razem, gdy zostanie wywołany jeden z mutujących interfejsów API
void Example() { var map = ImmutableMap.Create<int,string>(); map = map.Add(42,"foobar"); IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map); }
źródło
Możesz stworzyć klasę, która implementuje tylko częściową implementację słownika i ukrywa wszystkie funkcje add / remove / set.
Użyj słownika wewnętrznie, do którego klasa zewnętrzna przekazuje wszystkie żądania.
Jednakże, ponieważ twój słownik prawdopodobnie zawiera typy referencyjne, nie ma możliwości powstrzymania użytkownika przed ustawieniem wartości klas znajdujących się w słowniku (chyba że same te klasy są tylko do odczytu)
źródło
Nie sądzę, aby można było to łatwo zrobić ... jeśli Twój słownik jest częścią klasy niestandardowej, możesz to osiągnąć za pomocą indeksatora:
public class MyClass { private Dictionary<string, string> _myDictionary; public string this[string index] { get { return _myDictionary[index]; } } }
źródło
+1 Świetna robota, Thomas. Poszedłem o krok dalej w ReadOnlyDictionary.
Podobnie jak rozwiązanie Dale'a, chciałem usunąć
Add()
,Clear()
,Remove()
itp z IntelliSense. Ale chciałem zaimplementować moje obiekty pochodneIDictionary<TKey, TValue>
.Ponadto chciałbym, aby następujący kod się zepsuł: (Ponownie, rozwiązanie Dale'a też to robi)
ReadOnlyDictionary<int, int> test = new ReadOnlyDictionary<int,int>(new Dictionary<int, int> { { 1, 1} }); test.Add(2, 1); //CS1061
Linia Add () powoduje:
error CS1061: 'System.Collections.Generic.ReadOnlyDictionary<int,int>' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument
Wzywający może nadal przesyłać go do
IDictionary<TKey, TValue>
, aleNotSupportedException
zostanie podniesiony, jeśli spróbujesz użyć elementów członkowskich nie tylko do odczytu (z rozwiązania Thomasa).W każdym razie, oto moje rozwiązanie dla każdego, kto również tego chciał:
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); } } }
źródło
Teraz istnieją niezmienne kolekcje firmy Microsoft (
System.Collections.Immutable
). Pobierz je za pośrednictwem NuGet .źródło
public IEnumerable<KeyValuePair<string, string>> MyDictionary() { foreach(KeyValuePair<string, string> item in _mydictionary) yield return item; }
źródło
public IEnumerable<KeyValuePair<string, string>> MyDictionary() { return _mydictionary; }
To złe rozwiązanie, patrz na dole.
Dla tych, którzy nadal używają .NET 4.0 lub starszego, mam klasę, która działa tak jak ta w zaakceptowanej odpowiedzi, ale jest znacznie krótsza. Rozszerza istniejący obiekt Dictionary, przesłaniając (faktycznie ukrywając) niektórych członków, aby rzucali wyjątek po wywołaniu.
Jeśli obiekt wywołujący spróbuje wywołać Dodaj, Usuń lub inną operację mutacji, którą ma wbudowany słownik, kompilator zgłosi błąd. Używam atrybutów Przestarzałe, aby podnieść te błędy kompilatora. W ten sposób możesz zastąpić słownik tym ReadOnlyDictionary i natychmiast zobaczyć, gdzie mogą wystąpić problemy, bez konieczności uruchamiania aplikacji i czekania na wyjątki w czasie wykonywania.
Spójrz:
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(); } }
W tym rozwiązaniu występuje problem wskazany przez @supercat przedstawiony tutaj:
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); }
Zamiast podawać błąd w czasie kompilacji, jak się spodziewałem, lub wyjątek czasu wykonania, taki jak miałem nadzieję, ten kod działa bez błędów. Drukuje cztery liczby. To sprawia, że mój ReadOnlyDictionary jest ReadWriteDictionary.
źródło
Dictionary<TKey,TValue>
reklamacji bez żadnego kompilatora, a rzutowanie lub wymuszanie odwołania do typu zwykłego słownika usunie wszelkie zabezpieczenia.Dictionary
zeClone
sposobu, który przykuty doMemberwiseClone
. Niestety, podczas gdy efektywne klonowanie słownika powinno być możliwe poprzez klonowanie magazynów zapasowych, fakt, że magazyny zapasowe sąprivate
raczej niżprotected
oznacza, że nie ma sposobu, aby klasa pochodna mogła je sklonować; używanieMemberwiseClone
bez klonowania zapasów oznacza, że kolejne modyfikacje oryginalnego słownika zepsują klon, a modyfikacje wprowadzone w klonie zepsują oryginał.Dla każdego, kto przychodzi do tego pytania przy użyciu platformy .NET Core, zapoznaj się z System.Collections.Immutable .
W szczególności w przypadku niezmiennych słowników możesz użyć
ImmutableDictionary<TKey, TValue>.Builder
:public static ImmutableDictionary<string, string> ToReadOnly(this IDictionary<String, string> d) { var builder = new ImmutableDictionary<string, string>.Builder(); builder.AddRange(d); return builder.ToImmutable(); }
źródło