29

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.

bpeikes
  • 3,014
  • 6
  • 36
  • 71
  • 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

6 Answers6

34

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,117
  • 4
  • 36
  • 87
Matías Fidemraizer
  • 61,574
  • 17
  • 117
  • 195
  • @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
8

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
  • 18,286
  • 15
  • 82
  • 138
  • 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
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
  • 159,022
  • 53
  • 345
  • 499
  • 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
3

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
  • 143
  • 13
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
  • 726
  • 6
  • 20
-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
  • 805
  • 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