Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing nested generic Interfaces

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?

like image 583
Bryan Sumter Avatar asked Apr 27 '12 14:04

Bryan Sumter


People also ask

Can a generic implement an interface?

Only generic classes can implement generic interfaces. Normal classes can't implement generic interfaces.

How do you create a generic interface?

You can declare variant generic interfaces by using the in and out keywords for generic type parameters. ref , in , and out parameters in C# cannot be variant. Value types also do not support variance. You can declare a generic type parameter covariant by using the out keyword.

Which interface defines methods to control the different generic collections?

Generic Namespace. Contains interfaces and classes that define generic collections, which allow users to create strongly typed collections that provide better type safety and performance than non-generic strongly typed collections.

Which of the following syntax is used to declare a genetic interface?

The general syntax to declare a generic interface is as follows: interface interface-name<T> { void method-name(T t); // public abstract method. } In the above syntax, <T> is called a generic type parameter that specifies any data type used in the interface.


2 Answers

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/

like image 81
Eric Lippert Avatar answered Oct 13 '22 15:10

Eric Lippert


To fix your code, just change

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

to

public interface IB<out T> where T : IA { }
like image 20
CodingWithSpike Avatar answered Oct 13 '22 14:10

CodingWithSpike