3

I'm looking to see whether there's a way to easily expose my containers from c# classes without revealing unnecessary implementation details. I'm learning C# but I'm experienced in other OO languages, so I know that where possible it's better to expose base classes / interfaces rather than concrete types. What I'm doing is storing collections of key to (collection of object) in my class. I find that I can easily expose this as IDictionary(key, collection of object), but what I'd like is IDictionary(key, IEnumerable(object)). Hopefully a simple example will make it clearer.

class AddressBook
{
    public IDictionary<string, List<string>> Name2Addresses // ok, but I want IDictionary<string, IEnumerable<string>>
    {
        get { return name2Addresses; }
    }

    public IEnumerable<string> GetAddresses(string name)// ok, but I really just want the convenience of exposing container as a "one-liner"
    {
        return name2Addresses[name];
    }

    public AddressBook()
    {
        name2Addresses = new Dictionary<string,List<string>>();
        List<string> addresses = new List<string>(); // I'd prefer IList<string> addresses = ...
        name2Addresses.Add("Anne Best", addresses);
        addresses.Add("Home address");
        addresses.Add("Work address");
    }
    Dictionary<string, List<string>> name2Addresses;
}

I know that exposing container of derived as container of base is a tricky problem in OO because, e.g., you could add a different derived via the container base class. But hopefully because I want to expose the container as read-only via IEnumerable there might be an easy way to do this. This is the code I want.

    public IDictionary<string, IEnumerable<string>> Name2Addresses
    {
        get { return name2Addresses; }
    }

I tried adding a cast as the compiler complained about not having one

    public IDictionary<string, IEnumerable<string>> Name2Addresses
    {
        get { return (IDictionary<string, IEnumerable<string>>) name2Addresses; }
    }

but then I got an exception:

Additional information: Unable to cast object of type 'System.Collections.Generic.Dictionary2[System.String,System.Collections.Generic.IList1[System.String]]' to type 'System.Collections.Generic.IDictionary2[System.String,System.Collections.Generic.IEnumerable1[System.String]]'.

Any ideas? I was hoping there might be an easy / elegant way to do this, as on the whole it's been a joy to move from a lower-level OO language to C# where it's much quicker and easier to put what I want into practise. This isn't a show-stopper by any means as I can just reveal List rather than IEnum and trust myself not to abuse the List, but I would like to do things properly especially as I will hopefully have other people using my code one day.

PS I remember reading somewhere that C# has recently improved support for this sort of thing. I'm on VS2010 if this makes any difference.

Edit

I did some searching before asking, but after some more searching, I found this has been asked before, in a slightly different context, Why can't Dictionary<T1, List<T2>> be cast to Dictionary<T1, IEnumerable<T2>>?. So my question possibly reduces to whether there's a way of having a read-only IDictionary (see also Is there a read-only generic dictionary available in .NET?)

Another Edit

As Alex G / code4life suggested, this would work: without rolling my own read-only dictionary class I can create a new dictionary. E.g.

    public IDictionary<string, IEnumerable<string>> Name2AddressesNewDictionary
    {
        get
        {
            return name2Addresses.ToDictionary(na=>na.Key, na=>na.Value.AsEnumerable());
        }
    }

This incurs performance penalty every time accessed + space penalty of creating the new key-value pairs (and the dict itself). Plus extra developer time. Doesn't seem better than exposing a List, in general, if I'm using a property (especially as work is going on under the covers). But would be good as a method / in other circumstances.

Third Edit

As Jens Kloster suggested, change the definition of the container to use IEnumerable as the value

    Dictionary<string, IEnumerable<string>> name2Addresses;

and then cast when you need to in the implementation.

        ((IList<string>)name2Addresses["Anne Best"]).Add("Alternative address");
Community
  • 1
  • 1
TooTone
  • 7,129
  • 5
  • 34
  • 60
  • Your proposed solution is way too work-intensive. I would suggest either defining a `Dictionary>` or using `name2Addresses.ToDictionary(k-=>key, v=>v.Value.AsEnumerable())` approach instead - these are both 1-liner fixes to your problem. – code4life Apr 18 '13 at 12:54
  • yes, I broadly agree with what you write and am amending my question. I'm just having trouble editing fast enough to keep up with all the helpful suggestions! (first time I've asked a question on SO, v pleased with the response) – TooTone Apr 18 '13 at 12:59

5 Answers5

4

You can't cast, due to covariance/contravariance, but you can recreate the dictionary in the format that you want using LINQ:

using System.Linq; ...

public IDictionary<string, IEnumerable<string>> Name2Addresses
{
    get
    {
        return name2Addresses.ToDictionary<string, IEnumerable<string>>(
            kvp => kvp.Key,
            kvp => kvp.Value.AsEnumerable());
    }
}

Note: this creates a new dictionary object!

On a more general design-related note: any time you find yourself combining data structures in this way, it is usually worthwhile defining your own data structure to provide clarity and expose the necessary methods that you will want for traversing the structure, and performing common operations.

Alex
  • 7,639
  • 3
  • 45
  • 58
  • thanks, I was editing my question at the same time -- this is a good way to consider doing it. – TooTone Apr 18 '13 at 12:49
  • It does create a new dictionary, but you should note that the element instances are (er.. usually are) not recreated. – code4life Apr 18 '13 at 12:50
  • Your 'note' is important here. Many solutions (incl ones posted here) will recreate a dictionary which is horribly inefficient. – BJury Mar 13 '14 at 13:19
3

try changing the field to use IEnumerable instead of List.

Dictionary<string, IEnumerable<string>> name2Addresses;

then you should have no problem having a property like this:

 public IDictionary<string, IEnumerable<string>> Name2Addresses
 {
   get { return name2Addresses; }
 }

and you can still do this:

 public AddressBook()
    {
        name2Addresses = new Dictionary<string,IEnumerable<string>>(); //changed ere
        List<string> addresses = new List<string>(); 
        name2Addresses.Add("Anne Best", addresses);
        addresses.Add("Home address");
        addresses.Add("Work address");
    }

also - have a look at this post

Community
  • 1
  • 1
Jens Kloster
  • 11,099
  • 5
  • 40
  • 54
  • The issue with this approach is that you will need to cast the enumerable back to a `List` if you want to modify the list - and consuming classes can do the same. – Alex Apr 18 '13 at 12:48
  • @AlexG yes. but why would you do that. what would be the point of using `IEnumerable` if you are just going to cast it to a concrete type? :) – Jens Kloster Apr 18 '13 at 12:49
  • I think the idea is that at some point in the host class you may want to add or remove an address - but when you expose the dictionary externally, you don't want consumers to be able to do the same. To be honest, the best way is to create a custom data structure...! – Alex Apr 18 '13 at 12:51
  • @Jens Kloster, thanks I like this approach. As Alex G suggested I tried adding the following in my constructor to test I can still add to the dictionary: `((IList)name2Addresses["Anne Best"]).Add("Alternative address");`. That works. @Alex G, you're right that it is a disadvantage that clients can cast to an IList, but it seems like it would really be very naughty for clients to do that. Whereas, in the private implementation, it's ok isn't to rely on the implementation details. – TooTone Apr 18 '13 at 12:51
  • @AlexG I see you point. I *would* be a pain in #¤"% to add an address - even internally. Making a custom data structure would indeed be the right thing here - giving more control on who can add. – Jens Kloster Apr 18 '13 at 12:56
  • @TooTone: in that case, you should cast the `IList` to `IEnumerable` by making the `.AsEnumerable()` call. Like: `name2Addresses.ToDictionary(k=>k.Key, v=>v.Value.AsEnumerable())` – code4life Apr 18 '13 at 12:56
  • @code4life, thanks I'm new to .net and this syntax is still a bit unfamiliar. This is more concise than the LINQ equivalent. – TooTone Apr 18 '13 at 13:06
  • @Jens Kloster, I still like your suggested approach, at least in this case because for the cost of a little bit more complexity when I'm adding to the dictionary I can have a very simple and performant get method. In other circumstances, I'd recreate the dictionary, or code up a separate access class for it: these are all good suggestions (I'm going to use those suggestions elsewhere in my code base). – TooTone Apr 18 '13 at 13:10
1

You could look into the .AsEnumerable() extension method that is available through System.Linq:

http://msdn.microsoft.com/en-us/library/bb335435.aspx

Dietz
  • 578
  • 5
  • 14
  • thanks, not sure this helps though, not sure how I would use it to do what I want to. Your answer did inspire me to search in a slightly different way for an answer to my question, hence the edit to my original post. – TooTone Apr 18 '13 at 12:30
1

Indeed, the best way out would be a readonly covariant dictionary interface, but there isn't one. However you can easily write a readonly implementation of IDictionary which would wrap the underlying dictionary and cast elements to IEnumerable<T>.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • thanks, surprised that microsoft or a.n.other haven't done this as it seems to be a common problem and I understand the covariance/contravariance support has gotten better recently. – TooTone Apr 18 '13 at 12:50
  • I agree, it's a covariance problem. Which is why defining the dictionary to use `IEnumerable` is probably the wiser thing to do. – code4life Apr 18 '13 at 12:51
  • answering my own question as to why it hasn't been done already, thanks to the link that Jens posted: http://stackoverflow.com/a/7507943/834521 – TooTone Apr 18 '13 at 13:13
1

I think you're just going to have to LINQ-ify the dictionary again.

public IDictionary<string, IEnumerable<string>> Name2Addresses    
{
  get
  {
    return name2Addresses.ToDictionary(d => d.Key, d => d.Value.AsEnumerable());
  }
}

Conversely, redefine the dictionary to use IEnumerable - this might be the best way to do it.:

Dictionary<string, IEnumerable<string>> name2Addresses;
code4life
  • 15,655
  • 7
  • 50
  • 82