Pregunta

I have a method with the signature

public void Foo(IDictionary<string, IEnumerable<string>> data)
{
}

and wish to pass in

private Dictionary<string, HashSet<string>> myInput = 
    new Dictionary<string, HashSet<string>>()
{
};      

The line

Foo(myInput);

yields the compiler error:

Argument 1: cannot convert from System.Collections.Generic.Dictionary<string,System.Collections.Generic.HashSet<string>> to System.Collections.Generic.IDictionary<string,System.Collections.Generic.IEnumerable<string>>

Dictionary<K,V> implements IDictionary<K,V> and HashSet<T> implements IEnumerable<T>.

  • Why is the compiler unable to perform the conversion?
  • How can I create an instance of data that I can pass to Foo?

NOTE

If I change the signature to

public void Foo(IDictionary<string, HashSet<string>> data)

the compilation succeeds. However, I do not need any knowledge that a concrete type such as HashSet<T> is being passed in. Any IEnumerable<T> will do.

UPDATE

The following compiles:

public void Foo(IDictionary<string, IEnumerable<string>> data)
{
    List<string> item = new List<string>() { "foo", "bar", "baz" };
    data.Add("key", item);
    HashSet<string> item2 = new HashSet<string>() { "quu" };
    data.Add("key2", item2);
}

so clearly data can accept mixed-type values that all implement IEnumerable<T>

¿Fue útil?

Solución

The reason why this isn’t work is because of restrictions about covariance and contravariance in generics. The best way to explain this is to show an example of why this has to fail.

Assuming Foo has a valid signature, the type system promises that the following works:

var myInput = new Dictionary<string, HashSet<string>>();

// assuming a valid signature for `Foo`
Foo(myInput);

// according to the type of `myInput` the following MUST work
HashSet<string> item = myInput["foo"];
item.Add("baz");

That is what absolutely has to work. So anything Foo does has to make sure that this still works.

So ignoring above for a moment, let’s assume the following valid implemention for Foo:

public void Foo (IDictionary<string, IEnumerable<string>> data)
{
    List<string> item = new List<string>(){ "foo", "bar" };
    data.Add("foo", item);
}

Because List<string> implements IEnumerable<string>, adding a list object to the dictionary that stores IEnumerable<string>s absolutely works. Again: Above is a valid implementation of Foo for its signature.

However, if we now combine both code segments, it falls apart: The object stored at key "foo" is not a HashSet<string> but a List<string>. So the assignment at HashSet<string> item = myInput["foo"] would fail. But here is a conflict! The type system should ensure that the assignment works no matter what happens inside Foo; but the implementation of Foo is also completely valid for its signature.

So instead of making some arbitrary and intransparent rules here, this is simply not allowed. The type system just prevents such calls of Foo with an incompatible parameter. And no, because IDictionary is invariant, it isn’t possible to work around this restriction.

Otros consejos

@poke's answer gives you the why, so I won't spend much time on that.

You can manage this restriction by using a generic method with a where restriction like this:

public void Foo<T>(IDictionary<string, T> data)
    where T : IEnumerable<string>
{
}

Now your Foo method accepts any type of object that implements IEnumerable<string>, regardless of the solid type of the enumerable. You can do anything you like to the dictionary apart from add items with content.

But let's say that you do want to add some items:

public void Foo<T>(IDictionary<string, T> data)
    where T : ICollection<string>, new()
{
    var col = new T();
    col.Add("bar");

    data["col"] = col;
}

Here's a quick test:

var a = new Dictionary<string, HashSet<string>>();
var b = new Dictionary<string, List<string>>();
var c = new Dictionary<string, LinkedList<string>>();

Foo(a);
Foo(b);
Foo(c);

Compiles and runs fine. All three dictionaries end up with a new collection of the appropriate solid type containing the string bar.

What you can't do is add a collection of the wrong type to the Dictionary. Ever. Whatever operation you try to perform on the object must be consistent with the solid type of that object. Which is why the Foo method needs to be generic in this case.

As per my comment, here's the way to make your call work with a little help from LINQ:

Foo(myInput.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.AsEnumerable()));
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top