在应该隐式关系时,如何避免显式定义泛型类型参数?

时间:2019-01-21 04:37:06

标签: c# generics

我正在研究这个例子:

食物分类:

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> { }

我感到很烦的是,对于一个奶牛场,我必须定义食物的类型,因为食物的类型应该已经由母牛定义了。

我觉得我在这里想念什么。理想情况下,我希望能够仅根据其饲养的动物来定义一个农场,然后由该动物定义食物类型。不幸的是,您必须在未指定食物类型T的情况下将Animal约束应用于U。

我想念什么?

2 个答案:

答案 0 :(得分:4)

  

我感到很烦的是,对于一个奶牛场,我必须定义食物的类型,因为食物的类型应该已经由母牛定义了。

那是您的逻辑错误; C#类型系统完全不知道“农场”,“母牛”和“食物”在您的脑海中有那些关系。您的想法是“母牛吃食物,因此农场应该通过食物自动进行参数设置”,但是农场也会生产食物;编译器如何知道您打算将“食物”与牛吃的食物逻辑地联系起来,而不是与农场生产的食物联系起来?

  

我想念什么?

您正在尝试将业务逻辑放入类型系统中。不要那样做如您所知,这会带来麻烦。

我写了一系列博客文章,介绍了许多错误的解决方法,而您本人对此也很了解。您可能会发现它很有趣:https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/

答案 1 :(得分:3)

不幸的是,这是通用类型系统在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);
        }
    }
}

现在的问题是,您可以拥有一个DairyFarmFoodSupply的{​​{1}}(例如)-只有您不能将Meat馈送到{ {1}},因为他们只吃Meat

需要同时具有两个类型参数以使用泛型-编译器无法从Cow推断出Grass的特定类型。