5

I have the following code

public class TestAdaptor
{
    private interface ITargetClass
    {
        Guid Id { get; }

        string Name { get; }
    }

    private class MyTargetClass : ITargetClass
    {
        public Guid Id { get; private set; }

        public string Name { get; private set; }

        public MyTargetClass(MySourceClass source)
        {
        }
    }

    private class MySourceClass
    {
        public Guid Id { get; set; }

        public string Name { get; set; }
    }

    private Dictionary<Guid, IEnumerable<ITargetClass>> ConvertItems(Dictionary<Guid, IEnumerable<MySourceClass>> source)
    {
        return source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Select(v => new MyTargetClass(v)));
    }
}

However this will not compile as the ToDictionary line causes the following error

Cannot implicitly convert type
'System.Collections.Generic.Dictionary<System.Guid,System.Collections.Generic.IEnumerable<TestAdaptor.TestAdaptor.MyTargetClass>>'
to
'System.Collections.Generic.Dictionary<System.Guid,System.Collections.Generic.IEnumerable<TestAdaptor.TestAdaptor.ITargetClass>>'   ...\TestAdaptor.cs  38  20

Now it is clearly obvious that MyTargetClass implements ITargetClass but the compiler doesn't pick this up.

For now I am explicitly converting (ITargetClass)new MyTargetClass(v)

But why is this happening in the first place and is there a better way to resolve this?

nvoigt
  • 75,013
  • 26
  • 93
  • 142
Kezza
  • 736
  • 9
  • 25

2 Answers2

5

The compiler wont automatically convert IEnumberable<X> to IEnumerable<Y> even if X : Y because IDictionary is not covariant. The rationale for this is discussed here: IDictionary<,> contravariance? and IDictionary<TKey, TValue> in .NET 4 not covariant

As for getting around it, like you mentioned, you'll have to cast:

With Cast extension method:

 kvp => kvp.Value.Select(v => new MyTargetClass(v)).Cast<ITargetClass>()

Explicit cast:

 kvp => kvp.Value.Select(v => (ITargetClass) new MyTargetClass(v))

Update:

Just to expand on this because of the confusion between IEnumerable and IDictionary. IEnumerable is covariant. IDictionary is not.

This is just fine:

 IEnumerable<ITargetClass> list = new List<MyTargetClass>();

This is not:

 IDictionary<object, IEnumerable<ITargetClass>> dict = 
     new Dictionary<object, List<MyTargetClass>>();

IDictionary inherits from IEnumerable<KeyValuePair<TKey, TValue>>. At issue is the KeyValuePair which is not covariant, which makes IDictionary not covariant.

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
Philip Pittle
  • 11,821
  • 8
  • 59
  • 123
1

Select only reports what is being created and not a facet of the objects type. Let the select know what you are using via the as keyword.

Select(v => new MyTargetClass(v) as ITargetClass));

It is not the compiler's job to understand an intention of a developer, for a class may express many interfaces. One has to provide hints to the select statement which ultimately brings it in line with the return object required.

Otherwise you can filter the elements and return an IEnumerable of the interface using OfType IEnumerable extension to return what is required by your method.

.Select(v => new MyTargetClass(v))
.OfType<ITargetClass>()
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • I suppose the real issue was not that I should explicitly declare the type but the fact that in some cases I don't have to and the compiler will actually understand the required cast. This is not an issue related to LINQ as it is the Dictionary itself that is causing the compile error. I guess my confusion is more about consistency, why can I imply the type in one case (List) but not in others (Dictionary). Anyway Philips answer explains more of this. Thanks anyway. – Kezza Aug 21 '14 at 11:08
  • @Kezza as long as you got an answer which works that is what counts. :-) – ΩmegaMan Aug 21 '14 at 13:25