Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq ToDictionary will not implicitly convert class to interface

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?

like image 921
Kezza Avatar asked Aug 05 '14 10:08

Kezza


2 Answers

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.

like image 132
Philip Pittle Avatar answered Nov 20 '22 14:11

Philip Pittle


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>()
like image 1
ΩmegaMan Avatar answered Nov 20 '22 15:11

ΩmegaMan