Reputation: 51
I am trying to learn how to create generic classes with c#. Can someone explain why I get a compile error when I run this program.
I have created the IZooAnimal interface. All zoo animals will implement this interface.
public interface IZooAnimal
{
string Id { get; set; }
}
public class Lion : IZooAnimal
{
string Id { get; set; }
}
public class Zebra : IZooAnimal
{
public string Id { get; set; }
}
The ZooCage will hold animals of the same Type
public class ZooCage<T> where T : IZooAnimal
{
public IList<T> Animals { get; set; }
}
The zoo class have cages
public class Zoo
{
public IList<ZooCage<IZooAnimal>> ZooCages { get; set; }
}
The program that uses the classes
class Program
{
static void Main(string[] args)
{
var lion = new Lion();
var lionCage = new ZooCage<Lion>();
lionCage.Animals = new List<Lion>();
lionCage.Animals.Add(lion);
var zebra = new Zebra();
var zebraCage = new ZooCage<Zebra>();
zebraCage.Animals = new List<Zebra>();
zebraCage.Animals.Add(zebra);
var zoo = new Zoo();
zoo.ZooCages = new List<ZooCage<IZooAnimal>>();
zoo.ZooCages.Add(lionCage);
}
}
When I compile I get the following error:
Error 2 Argument 1: cannot convert from 'ConsoleApplication2.ZooCage<ConsoleApplication2.Lion>
' to 'ConsoleApplication2.ZooCage<ConsoleApplication2.IZooAnimal>
'
What changes do I have to do in order to make my program run?
Upvotes: 5
Views: 709
Reputation: 7830
You should define your lists not with the concrete type that implements the interface, but with the interface:
var lionCage = new ZooCage<IZooAnimal>();
lionCage.Animals = new List<IZooAnimal>();
Then your code will work as expected.
The initial code did not work, because it is not allowed to convert concrete types to a generalised type (as @default.kramer pointed out covariance and contravariance).
The solution that i came up is following:
// your ZooCage is still generic
public class ZooCage<T>
{
// but you declare on creation which type you want to contain only!
private Type cageType = null;
public ZooCage(Type iMayContain)
{
cageType = iMayContain;
animals = new List<T>();
}
// check on add if the types are compatible
public void Add(T animal)
{
if (animal.GetType() != cageType)
{
throw new Exception("Sorry - no matching types! I may contain only " + cageType.ToString());
}
animals.Add(animal);
}
// should be generic but not visible to outher world!
private IList<T> animals { get; set; }
}
This code allows you to do:
var lion = new Lion();
var lionCage = new ZooCage<IZooAnimal>(typeof(Lion));
lionCage.Add(lion);
var zebra = new Zebra();
var zebraCage = new ZooCage<IZooAnimal>(typeof(Zebra));
zebraCage.Add(zebra);
But it will throw an error on:
zebraCage.Add(lion);
Now the zoo can be safely extended.
Upvotes: 2
Reputation: 3941
@DanielMann's answer is quite good, but suffers from one drawback: the original IList
interface cannot be used with the ICage
interface. Instead, the ICage has to expose a ReadOnlyCollection, and expose a new method called CageAnimal.
I've also re-written the code using a similar approach. My ICage
implementation is much weaker, but it allows you to stick with IList
semantics inside.
public interface IZooAnimal
{
string Id { get; set; }
}
public class Lion : IZooAnimal
{
public string Id { get; set; }
}
public class Zebra : IZooAnimal
{
public string Id { get; set; }
}
public interface ICage
{
IEnumerable<IZooAnimal> WeaklyTypedAnimals { get; }
}
public class Cage<T> : ICage where T : IZooAnimal
{
public IList<T> Animals { get; set; }
public IEnumerable<IZooAnimal> WeaklyTypedAnimals
{
get { return (IEnumerable<IZooAnimal>) Animals; }
}
}
public class Zoo
{
public IList<ICage> ZooCages { get; set; }
}
class Program
{
static void Main(string[] args)
{
var lion = new Lion();
var lionCage = new Cage<Lion>();
lionCage.Animals = new List<Lion>();
lionCage.Animals.Add(lion);
var zebra = new Zebra();
var zebraCage = new Cage<Zebra>();
zebraCage.Animals = new List<Zebra>();
zebraCage.Animals.Add(zebra);
var zoo = new Zoo();
zoo.ZooCages = new List<ICage>();
zoo.ZooCages.Add(lionCage);
}
}
Upvotes: 4
Reputation: 59020
Since you want to have multiple cages, but each type of cage can only hold one animal, your model is slightly off.
I rewrote the code as follows:
IZooAnimal
is unchanged. ICage
that accepts any type of IZooAnimal
. That allows you to have a strongly-typed cage for every type of animal. Cage
concrete implementation of ICage
. Cage
is generic, but you could just as easily make it an abstract class and then make animal-specific cage implementations. For example, if your zebra needs to be fed grass, and your lion needs to be fed meat, you could specialize the implementations of their cages.Here's the complete code:
public interface IZooAnimal
{
string Id { get; set; }
}
public interface ICage<out T> where T : IZooAnimal
{
IReadOnlyCollection<T> Animals { get; }
}
public class Cage<T> : ICage<T> where T: IZooAnimal
{
private readonly List<T> animals = new List<T>();
public IReadOnlyCollection<T> Animals
{
get
{
return animals.AsReadOnly();
}
}
public void CageAnimal(T animal)
{
animals.Add(animal);
}
}
public class Lion : IZooAnimal
{
public string Id { get; set; }
}
public class Zebra : IZooAnimal
{
public string Id { get; set; }
}
public class Zoo
{
public IList<ICage<IZooAnimal>> Cages { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
var lion = new Lion();
var zebra = new Zebra();
var lionCage = new Cage<Lion>();
lionCage.CageAnimal(lion);
var zebraCage = new Cage<Zebra>();
zebraCage.CageAnimal(zebra);
var zoo = new Zoo();
zoo.Cages.Add(lionCage);
zoo.Cages.Add(zebraCage);
}
}
Upvotes: 2