How do I avoid explicitly defining generic type arguments when the relationship should be implicit?

前端 未结 2 1831
面向向阳花
面向向阳花 2021-01-28 16:39

I was working on this example:

Food classes:

public class Food { }

public class Meat : Food { }

public class Grass : Food { }

Animal

相关标签:
2条回答
  • 2021-01-28 17:18

    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/

    0 讨论(0)
  • 2021-01-28 17:32

    Unfortunately this is how the generic type system works in C#.

    You have this problem because:

    • The Animal class has the Feed(T food) method. This is key.
    • The Farm class has a T FoodSupply in order to feed the Animals within it.
    • The 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>.

    0 讨论(0)
提交回复
热议问题