views:

2718

answers:

8

I'm returning a reference to a dictionary in my read only property. How do I prevent consumers from changing my data? If this were an IList I could simply return it AsReadOnly. Is there something similar I can do with a dictionary?

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

You could create a class that only implements a partial implementation of the dictionary, and hides all the add/remove/set functions.

Use a dictionary internally that the external class passes all requests to.

However, since your dictionary is likely holding reference types, there is no way you ca stop the user from setting values on the classes held by the dictionary (unless those classes themselves are read only)

Jason Coyne
+4  A: 

No, but it would be easy to roll your own. IDictionary does define an IsReadOnly property. Just wrap a Dictionary and throw a NotSupportedException from the appropriate methods.

wekempf
+18  A: 

There is no class that wraps a Dictionary like the ReadOnlyCollection wraps a List. However, it shouldn't be difficult to create one.

Here is an example - there are many others if you Google for ReadOnlyDictionary.

Jeff Yates
A: 

I don't think there's an easy way of doing it...if your dictionary is part of a custom class, you could achieve it with an indexer:

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

  public string this[string index]
  {
    get { return _myDictionary[index]; }
  }
}
Jonas
I need to be able to expose the entire dictionary as well as an indexer.
vg1890
+1  A: 
shahkalpesh
Or you can do: `public IEnumerable<KeyValuePair<string, string>> MyDictionary() { return _mydictionary; }`
Pat
+1  A: 

None available in the BCL. However I published a ReadOnlyDictionary (named ImmutableMap) in my BCL Extras Project

In addition to being a fully immutable dictionary, it supports producing a proxy object which implements IDictionary and can be used in any place where IDictionary is taken. It will throw an exception whenever one of the mutating APIs are called

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

IsReadOnly on IDictionary<TKey,TValue> is inherited from ICollection<T> (IDictionary<TKey,TValue> extends ICollection<T> as ICollection<KeyValuePair<TKey,TValue>>). It is not used or implemented in any way ( and is in fact "hidden" through the use of explicitly implementing the associated ICollection<T> members ).

There are at least 3 ways I can see to solve the problem:

  1. Implement a custom read only IDictionary<TKey, TValue> and wrap / delegate to an inner dictionary as has been suggested
  2. Return an ICollection<KeyValuePair<TKey, TValue>> set as read only or an IEnumerable<KeyValuePair<TKey, TValue>> depending on the use of the value
  3. Clone the dictionary using the copy constructor .ctor(IDictionary<TKey, TValue>) and return a copy - that way the user is free to do with it as they please and it does not impact on the state of the object hosting the source dictionary. Note that if the dictionary you are cloning contains reference types ( not strings as shown in the example ) you will need to do the copying "manually" and clone the reference types as well.

As an aside; when exposing collections, aim to expose the smallest possible interface - in the example case it should be IDictionary as this allows you to vary the underlying implementation without breaking the public contract that the type exposes.

Neal
+5  A: 

Here's a simple implementation that wraps a dictionary :

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

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

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

    #region IDictionary<TKey,TValue> Members

    public void Add(TKey key, TValue value)
    {
        throw new NotSupportedException("This dictionary is read-only");
    }

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

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

    public bool Remove(TKey key)
    {
        throw new NotSupportedException("This dictionary is read-only");
    }

    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("This dictionary is read-only");
        }
    }

    #endregion

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

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        throw new NotSupportedException("This dictionary is read-only");
    }

    public void Clear()
    {
        throw new NotSupportedException("This dictionary is read-only");
    }

    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 bool Remove(KeyValuePair<TKey, TValue> item)
    {
        throw new NotSupportedException("This dictionary is read-only");
    }

    #endregion

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

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

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return (_dictionary as System.Collections.IEnumerable).GetEnumerator();
    }

    #endregion
}
Thomas Levesque