3

If we have field List<Dictionary<>>, how to expose it as a readonly property?

To example:

public class Test
{
    private List<Dictionary<string, object>> _list;
}

I can expose it like this

public ReadOnlyCollection<Dictionary<string, object>> List
{
    get { return _list.AsReadOnly(); }
}

but it is still possible to change directory:

var test = new Test();
test.List[0]["a"] = 3; // possible
test.List[0].Add("e", 33); // possible

Here is an attempt to make it readonly

public ReadOnlyCollection<ReadOnlyDictionary<string, object>> List
{
    get
    {
        return _list.Select(item =>
            new ReadOnlyDictionary<string, object>(item)).ToList().AsReadOnly();
    }
}

I think the problem with this approach is obvious: it's a new list of new dictionaries.

What I would like to have is something similar to List<>.AsReadOnly(), to have property act as a wrapper over _list.

Cᴏʀʏ
  • 105,112
  • 20
  • 162
  • 194
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • @oppassum: That seems to be a different question. This question here is about how to make sure the *elements* of the read-only list are also read-only (as per their public interface, at least). – O. R. Mapper Jul 06 '15 at 16:14
  • Yup, comment deleted. I actually just didn't thoroughly enough look at the code. – oppassum Jul 06 '15 at 16:19

3 Answers3

3

If you cannot create a new list of Dictionary objects, I would suggest to expose the items that you need from your class directly:

public IReadOnlyDictionary<string, object> this[int i] 
{
    get { return this._list[i]; }
}
//OR
public IReadOnlyDictionary<string, object> GetListItem(int i)
{
    return _list[i];
}

public int ListCount
{
    get { return this._list.Count; }
}  

Then use it like this:

var test = new Test();

var dictionary = test[0];
//OR
dictionary = test.GetListItem(0);

int count = test.ListCount;
Bas
  • 26,772
  • 8
  • 53
  • 86
  • Indexer, cool! Another thing (which I miss in my question) is what `Dictionary<>` implements `IReadOnlyDictionary<>`, so I don't need to create **new** item (not sure what `ReadOnlyDictionary` constructor actually does, maybe it's the same). – Sinatr Jul 06 '15 at 16:30
  • @Sinatr The standard `System.Collections.Generic.Dictionary` implements `IReadOnlyDictionary` in .NET 4.5 and higher. Are you using an old version of .NET? https://msdn.microsoft.com/en-us/library/hh136548(v=vs.110).aspx – Bas Jul 06 '15 at 16:33
  • I am using 4.5. I upvoted already for only this reason. Not sure how to use given method (evening, my head doesn't works anymore), will check it tomorrow. I like idea with indexer, but it *hides* list. You have to expose `Count` and can't use `foreach`... – Sinatr Jul 06 '15 at 16:44
3

I've created a List of ReadOnlyDictionary and populate it with the converted dictionaries, When i'm finished i'm converting the whole list into AsReadOnly.

Code:

public ReadOnlyCollection<ReadOnlyDictionary<string, object>> AsReadOnlyListAndElements
{
    get
    {
        var list = _list.Select(elem => new ReadOnlyDictionary<string, object>(elem));
        return list.ToList().AsReadOnly();
    }
}

Old solution:

You can create a wrapper similar to this with the methods you want to expose:

public class ReadOnlyDict<K, V>
{
    private Dictionary<K, V> dictionary;

    public ReadOnlyDict(Dictionary<K, V> dict)
    {
        dictionary = dict;
    }

    public V this[K key]
    {
        get
        {
            return dictionary[key];
        }
    }

    // Add more methods per your request
}

And an extension method:

namespace System.Collections.Generic
{
    public static class DictionaryExt
    {
        public static ReadOnlyDict<K, V> ToReadOnlyDictionary<K, V>(this Dictionary<K, V> dict)
        {
            return new ReadOnlyDict<K, V>(dict);
        }
    }
}

And them your code will look something like this:

Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("2", "2");
dict.Add("3", "3");

var roDict = dict.ToReadOnlyDictionary();
var a = roDict["2"];
Orel Eraki
  • 11,940
  • 3
  • 28
  • 36
  • I have not tested but are you sure this would prevent test.List[0]["3"] = "4"; – paparazzo Jul 06 '15 at 16:29
  • 1
    @Blam Yes, as there is no setter on the indexer. – Servy Jul 06 '15 at 16:30
  • For a list of dictionaries I would have to call `ToReadOnlyDictionary()` for each item in the list, right? Why not simply cast `Dictionary` to `IReadOnlyDictionary` as per @Blam solution? I don't see (or simply don't understand) the point of this answer. Orel, can you show how would my property `List` looks like? – Sinatr Jul 06 '15 at 16:36
  • I had a question - not a solution. – paparazzo Jul 06 '15 at 16:50
  • @Sinatr - Assuming dics is a list of dictionaries, you can do something like this: `dics.Select(dic => dic.ToReadOnlyDictionary()).ToList();` . If you want, you can convert the list to be readonly as well. – Amir Popovich Jul 06 '15 at 17:15
  • @Creysys That does seem to be the desired behavior; the requirement is to not allow the *dictionary* to be mutated, not to prevent the mutation of any of the items in it. – Servy Jul 06 '15 at 17:52
  • @Sinatr, Amir Popovich answered your question. – Orel Eraki Jul 06 '15 at 20:13
  • I'll ask again, how is that **different** from `IReadOnlyDictionary` (to prevent dictionary and its pairs from changing) and from my original linq, where I **create new ReadOnlyDictionary** for each element (as I would have to do with `ReadOnlyDic`. Who is upvoting this answer? For that extension method? Really? Thanks alot! Now I feel indeed very stupid. – Sinatr Jul 07 '15 at 07:49
  • @AmirPopovich, can you see a *difference* in your and my linq (tip: you forgot `AsReadOnly()`, but then they are equal in what they do)? Both creates new list, both uses wrappers to hide mutable dictionary (I used `ReadOnlyDictionary<>` and your query creates `ReadOnlyDic<>`). Here is my question, **why** should I use `ReadOnlyDic<>` if there is already `ReadOnlyDictionary<>` lol? – Sinatr Jul 07 '15 at 07:59
  • @Sinatr, You want the List to be immutable + the dictionaries inside of it ? – Orel Eraki Jul 07 '15 at 08:44
  • @OrelEraki, this is exactly my question from yesterday. See my answer, I am not yet marking it (maybe it has some flaws or there is indeed a way to make a *wrapper* over `_list` to expose its content, including dictionaries, as read-only, single property is better than 3 as for now). The `_list` should be mutable for private methods of `Test`, but users of this class should get everything as read-only. – Sinatr Jul 07 '15 at 08:53
  • @Sinatr, No problem, sorry my bad. I'll make one right now. – Orel Eraki Jul 07 '15 at 08:54
  • You shouldn't remove upvoted content of your answer (maybe some people find it useful). And your edit looks... exactly as code in my question. Can you read my question again please? – Sinatr Jul 07 '15 at 09:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82581/discussion-between-orel-eraki-and-sinatr). – Orel Eraki Jul 07 '15 at 10:32
  • @Sinatr - The difference is that this dictionary wont let you add items in compile time since the indexer exposes only a getter. Your dictionary will throw an exception in runtime as you can see here: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=29. I have no idea why Orel updated his answer since his implementation was ok. You should check out the System.Collections.Generic.IReadOnlyDictionary interface since it seems to be what you are looking for here: : https://msdn.microsoft.com/en-us/library/hh136548(v=vs.110).aspx – Amir Popovich Jul 07 '15 at 11:47
  • @AmirPopovich, yes I got it from Bas answer, for me `ReadOnlyDictionary<>` and `IReadOnlyDictionary<>` are the same. They simply make it impossible to modify dictionary instance. If there are some *details* of implementation, then they have to be **clearly stated**. To understand **why** this and not that. I'd really like if you have a look at my answer to 1) see what I was looking for 2) tell me if Orel answer is actually does anything (first version of answer or current one) to solve my issue. Otherwise I simply do not understand the point. – Sinatr Jul 07 '15 at 12:05
  • @`ReadOnlyDictionary<>` and `IReadOnlyDictionary<>` aren't the same. One throws an exception in runtime!!! and one will fail in compile!!! time. Since your question's example had the `ReadOnlyDictionary<>`, which does allow you to assign something in compile time and will fail in runtime - Orel's(original) answer was to show you how you can restrict this in compile time as you requested. – Amir Popovich Jul 07 '15 at 12:14
0

There is no way to avoid creating new list, because Dictionary items has to be converted to IReadOnlyDicionary (by using Cast or Select). So here comes the question

Why it should be a list?

ReadOnlyList is only used to access items by using index or in foreach. So, why not simply provide that?

@Bas solution with indexer and simple property with yield return produces the final solution:

public class Test
{
    private List<Dictionary<string, object>> _list = new List<Dictionary<string, object>>();

    public IEnumerable<IReadOnlyDictionary<string, object>> Items
    {
        get
        {
            foreach (var item in _list)
                yield return item;
        }
    }

    public IReadOnlyDictionary<string, object> this[int i]
    {
        get { return _list[i]; }
    }

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

Dictionary (and list) is accessible as readonly from outside like this:

var test = new Test();
Console.WriteLine(test[0]["a"]); // direct access
foreach (var item in test.Items) // enumeration
    foreach(var dic in item)
        Console.WriteLine(dic);
Sinatr
  • 20,892
  • 15
  • 90
  • 319