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/