Implementing nested generic Interfaces
Asked Answered
C

3

14

I have the following Classes / Interfaces:

// Model
public class A : IA { }
// ModelLogic
public class B : IB<A> { }

// Model Interface
public interface IA { }
// ModelLogic Interface
public interface IB<T> where T : IA { }

I try to create a new instance using the following code:

IB<IA> foo = new B();

I am getting the following error:

Cannot implicitly convert type 'B' to 'IB<IA>'. An explicit conversion exists (are you missing a cast?)

Can someone please explain why this is not possible?

Coyne answered 27/4, 2012 at 14:32 Comment(3)
What version of C# are you using?Aleras
Covariance and Contravariance FAQ and this blog series by Eric LippertAleras
B is an IB<A>, not an IB<IA>.Maiamaiah
S
45

OK, let's replace A with Fish, IA with IAnimal, B with Aquarium, and IB<T> with IContainer<T>. And we'll add a member to IContainer<T>, and a second implementation of IAnimal:

// Model
public class Fish : IAnimal { }
public class Tiger : IAnimal { }
// ModelLogic
public class Aquarium : IContainer<Fish> 
{ 
    public Fish Contents { get; set; }
}

// Model Interface
public interface IAnimal { }
// ModelLogic Interface
public interface IContainer<T> where T : IAnimal 
{ 
    T Contents { get; set; }
}

IContainer<IAnimal> foo = new Aquarium(); // Why is this illegal?
foo.Contents = new Tiger(); // Because this is legal!

You can put a Tiger into foo -- foo is typed as a container that can contain any animal. But you can only put a Fish into an Aquarium. Since the operations you can legally perform on an Aquarium are different than the operations you can perform on an IContainer<IAnimal>, the types are not compatible.

The feature you want is called generic interface covariance and it is supported by C# 4, but you have to prove to the compiler that you will never put a tiger into your fish tank. What you want to do is:

// Model
public class A : IA { }
// ModelLogic
public class B : IB<A> { }

// Model Interface
public interface IA { }
// ModelLogic Interface
public interface IB<out T> where T : IA { }

Notice the covariance annotation on IB. This out means that T can only be used as an output, not as an input. If T is only an output then there is no way for someone to put a tiger into that fish tank because there is no "put into" property or method possible.

I wrote a number of blog articles while we were adding that feature to C#; if you are interested in the design considerations that went into the feature, see:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

Snakemouth answered 27/4, 2012 at 14:41 Comment(3)
Invalid variance: The type parameter 'T' must be invariantly valid on 'xx.IContainer<T>.Contents'. 'T' is covariant. I am getting this error. I am new to covariant stuff. What does the error mean?Indictable
@Sandeep: You are somehow using T in an input position when you have said that you are only going to use it in an output position. Is Contents a property with a setter? If it is then clearly T is being used in an input position, and therefore the interface cannot be made covariant in T.Snakemouth
Of all the variance-related questions on SO, this is by far the best answer that makes the most sense. +1Stilla
P
1

To fix your code, just change

public interface IB<T> where T : IA { }

to

public interface IB<out T> where T : IA { }
Piling answered 27/4, 2012 at 14:43 Comment(0)
P
0

It's not easy to see when you have empty interfaces. Consider you have one method M in interface IB:

public interface IB<T> where T : IA 
{ 
    void M(T t); 
}

And here is implementation of B:

public class B : IB<A>
{
    public void M(A t)
    {
        // only object of type A accepted 
    }
}

Then you have object C, which also implements IA:

public class C : IA { } 

So, if your code would be possible, then you could call:

IB<IA> foo = new B();
foo.M(new C());

Problem is that class B accepts only objects of type A. Error!

Parkinson answered 27/4, 2012 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.