I was working on this example:
Food classes:
public class Food { }
public class Meat : Food { }
public class Grass : Food { }
Animal
And I found it annoying that for a dairy farm I had to define the type of food, since the type of food should already be defined by the cow.
That's your logical error right there; the C# type system has no idea whatsoever that "farm", "cow" and "food" have those relationships in your mind. Your thought is "cows eat food, therefore the farm should be parameterized by food automatically", but farms also produce food; how does the compiler know that you intend "food" to be logically connected to the food eaten by the cows but not the food produced by the farm?
What am I missing?
You're attempting to put business logic into your type system. Don't do that; as you've discovered, it makes for trouble.
I wrote a series of blog articles about some of the many ways to do it wrong, and you're well on this path yourself. You might find it entertaining reading: https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/
Unfortunately this is how the generic type system works in C#.
You have this problem because:
Animal
class has the Feed(T food)
method. This is key.Farm
class has a T
FoodSupply in order to feed the Animal
s within it.Farm
class needs to call Feed(T food)
on the Animal
but it can't do that without knowing what T
is.You can get around this with interfaces. Say you had an IAnimal
interface:
public interface IAnimal
{
void Feed(Food food);
}
then Animal<T>
could implement it:
public abstract class Animal<TFood> : IAnimal where TFood : Food
{
public void Feed(TFood food)
{
// we either need to check for null here
}
public void Feed(Food food)
{
// or we need to check food is TFood here
Feed(food as TFood);
}
}
Then you can change your Farm
class to get rid of the generics altogether:
public abstract class Farm<TAnimal> where TAnimal : IAnimal
{
public List<TAnimal> Animals;
public Food FoodSupply;
public void FeedAnimals()
{
foreach ( var animal in Animals )
{
animal.Feed(FoodSupply);
}
}
}
The problem now is that you can have a DairyFarm
(say) for which the FoodSupply
is Meat
- only you can't feed a Meat
to a Cow
as they only eat Grass
.
You need to have both type arguments to make use of generics - the compiler can't infer the specific type of Food
from the Animal<T>
.