Я работал над этим примером:
Классы питания:
public class Food { }
public class Meat : Food { }
public class Grass : Food { }
Классы животных:
public abstract class Animal<TFood>
where TFood : Food
{
public void Feed(TFood food) { }
}
public abstract class Carnivore : Animal<Meat> { }
public abstract class Herbivore : Animal<Grass> { }
public class Cow : Herbivore { }
Классы фермы:
public abstract class Farm<TAnimal, TFood>
where TAnimal : Animal<TFood>
where TFood : Food
{
public List<TAnimal> Animals;
public TFood FoodSupply;
public void FeedAnimals()
{
foreach ( var animal in Animals )
{
animal.Feed(FoodSupply);
}
}
}
public class DariyFarm : Farm<Cow, Grass> { }
И меня раздражало, что для молочной фермы мне приходилось определять тип корма, так как тип корма уже должна определять корова.
Я чувствую, что здесь что-то упускаю. В идеале я хотел бы иметь возможность определять ферму только с точки зрения того, какое животное она выращивает, и тогда тип пищи будет определяться животным. К сожалению, вы не можете применить ограничение Animal к U, не указав тип еды T.
Что мне не хватает?





К сожалению, именно так работает общая система типов в C#.
У вас есть эта проблема, потому что:
Animal имеет метод Feed(T food). Это ключ.Farm имеет T FoodSupply, чтобы кормить внутри него Animal.Farm должен вызывать Feed(T food) на Animal, но он не может этого сделать, не зная, что такое T.Вы можете обойти это с помощью интерфейсов. Скажем, у вас был интерфейс IAnimal:
public interface IAnimal
{
void Feed(Food food);
}
тогда Animal<T> мог бы реализовать это:
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);
}
}
Затем вы можете изменить свой класс Farm, чтобы полностью избавиться от дженериков:
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);
}
}
}
Теперь проблема в том, что у вас может быть DairyFarm (скажем), для которого FoodSupply является Meat, но вы не можете скормить MeatCow, поскольку они едят только Grass.
Вы необходимость должны иметь оба аргумента типа для использования дженериков — компилятор не может вывести конкретный тип Food из Animal<T>.
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.
Это ваша логическая ошибка прямо здесь; система типов С# понятия не имеет, что «ферма», «корова» и «еда» имеют эти отношения в вашем уме. Ваша мысль такова: «коровы едят корм, поэтому ферма должна быть автоматически параметризована кормом», но фермы также производить корма; как компилятор узнает, что вы намерены логически связать «еду» с едой, которую едят коровы, а не с едой, произведенной на ферме?
What am I missing?
Вы пытаетесь внедрить бизнес-логику в свою систему типов. Не делай этого; как вы обнаружили, это создает проблемы.
Я написал серию статей в блоге о некоторых из многих способов сделать это неправильно, и вы сами уже на этом пути. Возможно, вам будет интересно почитать: https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/
Я не думаю, что это просто бизнес-логика, класс Cow имеет внутри себя тип Plant, я просто хочу, чтобы был способ получить к нему доступ. В любом случае, спасибо за замечания, я прочитаю эту статью.