36

I have a Dictionary<string, List<string>> and would like to expose the member as read only. I see that I can return it as a IReadOnlyDictionary<string, List<string>>, but I can't figure out how to return it as an IReadOnlyDictionary<string, IReadOnlyList<string>>.

Is there a way to do this? In c++ I'd just be using const, but C# doesn't have that.

Note that simply using a IReadOnlyDictionary does not help in this case, because I want the values to be read only as well. It appears the only way to do this is build another IReadOnlyDictionary, and add IReadOnlyList to them.

Another option, which I wouldn't be thrilled with, would be to create wrapper which implements the interface IReadOnlyDictionary>, and have it hold a copy of the original instance, but that seems overkill.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • 2
    What about a lookup instead? The Dictionary object has the `ToLookup()` extension. https://msdn.microsoft.com/en-us/library/bb460184(v=vs.110).aspx – John Bustos Aug 22 '16 at 18:41
  • Possible duplicate of [How to properly use IReadOnlyDictionary?](http://stackoverflow.com/questions/32560619/how-to-properly-use-ireadonlydictionary) – Dan Byström Aug 22 '16 at 18:47
  • Possible duplicate of [Is there a read-only generic dictionary available in .NET?](https://stackoverflow.com/questions/678379/is-there-a-read-only-generic-dictionary-available-in-net) – dana Aug 29 '17 at 18:40

7 Answers7

40

It would be as easy as casting the whole dictionary reference to IReadOnlyDictionary<string, IReadOnlyList<string>> because Dictionary<TKey, TValue> implements IReadOnlyDictionary<TKey, TValue>.

BTW, you can't do that because you want the List<string> values as IReadOnlyList<string>.

So you need something like this:

var readOnlyDict = (IReadOnlyDictionary<string, IReadOnlyList<string>>)dict
                        .ToDictionary(pair => pair.Key, pair => pair.Value.AsReadOnly());

Immutable dictionaries

This is just a suggestion, but if you're looking for immutable dictionaries, add System.Collections.Immutable NuGet package to your solution and you'll be able to use them:

// ImmutableDictionary<string, ImmutableList<string>>
var immutableDict = dict
           .ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableList());

Learn more about Immutable Collections here.

jpaugh
  • 6,634
  • 4
  • 38
  • 90
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • @HamletHakobyan Thanks! Immutables are powerful and learning them is worth the effort. Read-only collections aren't immutable, and OP has said "I want something like constants..." – Matías Fidemraizer Aug 22 '16 at 18:55
  • Won't "ToDictionary", create a copy of the whole dictionary? – bpeikes Aug 23 '16 at 18:08
  • @bpeikes Yes, and you need to do this way because you can't cast the list to read-only list with an explicit or implicit cast. – Matías Fidemraizer Aug 23 '16 at 21:39
  • you can cast to IReadOnlyDictionary but it can be casted back to Dictionary and modify the dictionary as the instance itself hasn't changed. – shtse8 Feb 17 '21 at 17:20
9

Given the fact that you're specifically looking for a read-only Dictionary<string, List<string>>, you're basically looking exactly for a Lookup.

The Dictionary object has a ToLookup() extension.

John Bustos
  • 19,036
  • 17
  • 89
  • 151
  • Not exactly. Reading the Remarks section of a Lookup class: `A Lookup resembles a Dictionary. The difference is that a Dictionary maps keys to single values, whereas a Lookup maps keys to collections of values.` – Mladen B. Jun 10 '19 at 13:53
  • 5
    Yes, @MladenB., but the OP asked for the value to be a `List`, so the lookup works perfectly. – John Bustos Jun 10 '19 at 15:47
4

I run into the same problem. I solved it on the following way.

List<string> list = new List<string>();

Dictionary<string, IReadOnlyCollection<string>> dic = new Dictionary<string, IReadOnlyCollection<string>>();

IReadOnlyDictionary<string, IReadOnlyCollection<string>> dicRo = new ReadOnlyDictionary<string, IReadOnlyCollection<string>>(dic);

 list.Add("Test1");

 dic["T"] = list.AsReadOnly();

 ist.Add("Test2");

This has the positiv effekt, that you

  • can still add items to the list
  • can still add items to the dictionary
  • can't edit the ReadOnlyDictionary
  • can't edit the ReadOnlyCollection
  • can't cast it into a Dictionary
  • can't cast it into a List
  • have your ReadOnlyDictionary always up to date

Maybe this will help someone.

RCP161
  • 199
  • 1
  • 13
3

First, you'll have to create a new dictionary with the desired content types:

var dicWithReadOnlyList = dic.ToDictionary(
    kv => kv.Key,
    kv => kv.Value.AsReadOnly());

Then you can just return the new dictionary, since IReadOnlyDictionary is a supertype of Dictionary.


Why do you need to do that? Because Dictionary<T, A> is not a supertype of Dictionary<T, B>, even if A is a supertype of B. Why? Consider the following example:

var dic = new Dictionary<T, B>();
Dictionary<T, A> dic2 = dic;      // Imagine this were possible...

dic2.Add(someT, someA);           // ...then we'd have a type violation here, since
                                  // dic2 = dic requires some B as the value.

In other words, TValue in Dictionary is not covariant. From an object-orientied point of view, covariance should be possible in the read-only version of the dictionary, but there are legacy issues in the .NET framework which prevent this (see the part starting with "UPDATE" in this question for details).

Community
  • 1
  • 1
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • Well it's just because `TValue` can't be covariant, but on this case I believe that `List` is `IReadOnlyList`. It's not exactly `A` and `B`. – Matías Fidemraizer Aug 22 '16 at 19:07
  • @MatíasFidemraizer: How is that different? If A is a supertype of B, B is A. – Heinzi Aug 22 '16 at 19:16
  • `Dictionary` wouldn't need to be covariant with respect to `TValue`, **`IReadOnlyDictionary` would need to be covariant with respect to `TValue`. – Servy Aug 22 '16 at 20:09
  • @Servy: True, that would be OK from an OO point of view. Unfortunately, there seem to be legacy issues in the framework which prevent that (see the [lower half of this question](http://stackoverflow.com/questions/2149589/idictionarytkey-tvalue-in-net-4-not-covariant) for an explanation). – Heinzi Aug 22 '16 at 20:11
  • @Heinzi That's `IDictionary`, not `IReadOnlyDictionary`. – Servy Aug 22 '16 at 20:11
  • @Servy: When I said **the lower half** of the question, i meant the part starting with "UPDATE". To quote: *"There will be an `IReadOnlyDictionary` in .NET 4.5, but it won't be covariant either :·/, because it derives from `IEnumerable>`, and `KeyValuePair` is not an interface and thus cannot be covariant."* – Heinzi Aug 22 '16 at 20:12
  • @Downvoter: Feedback to improve the answer is appreciated. – Heinzi Aug 22 '16 at 20:15
2

Not directly answering the question, but .NET 8 introduces FrozenDictionary.

The new System.Collections.Frozen namespace includes the collection types FrozenDictionary<TKey,TValue> and FrozenSet<T>. These types don't allow any changes to keys and values once a collection is created. That requirement allows faster read operations (for example, TryGetValue()). These types are particularly useful for collections that are populated on first use and then persisted for the duration of a long-lived service, for example:

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
0

If you want to return a read only dictionary but still be able to mutate the dictionary and list in your class you could use casting to get back the list type.

This example is a bit contrived, but shows how it could work.

public class MyClass
{
    Dictionary<string, IReadOnlyList<string>> _dictionary;
    public IReadOnlyDictionary<string, IReadOnlyList<string>> Dictionary { get { return _dictionary; } }

    public MyClass()
    {
        _dictionary = new Dictionary<string, IReadOnlyList<string>>();
    }

    public void AddItem(string item)
    {
        IReadOnlyList<string> readOnlyList = null;
        List<string> list = null;
        if (!_dictionary.TryGetValue(item, out readOnlyList))
        {
            list = new List<string>();
            _dictionary.Add(item, list);
        }
        else
            list = readOnlyList as List<string>;
        list.Add(item);
    }
}

If you goal is to have the property be immutable, then using a ReadOnlyDictionary would be the best option.

Patrick Huber
  • 756
  • 6
  • 21
-2

https://msdn.microsoft.com/en-us/library/acdd6hb7.aspx

You can use this to expose the object as readonly.

You could also use properties get; set; and only allow the get to be public.

But Matias answer seems to be more fitting.

TheNoob
  • 861
  • 2
  • 11
  • 25
  • 2
    readonly complex types like dictionaries can still be changed by using methods like `.Add()` or `.Remove()` – Lars Celie Aug 17 '18 at 09:48