使用此示例C#代码具有协方差/逆变量问题

时间:2013-07-17 23:58:39

标签: c# .net inheritance

我误解了继承中显而易见的事情。我已经创建了一个示例控制台应用程序,它具有一些可以相互继承的接口和类。

使用工厂,我正在尝试根据给定的密钥检索适当的服务。但编译器说它无法将具体类添加到我要求的通用字典中。

这是我正在尝试做的事情:

鉴于工厂,请通过keyname获取服务。然后在此服务上执行Search方法。

class Program
{
    static void Main(string[] args)
    {
        var animalFactory = new AnimalFactory();

        // Search for some fishes.
        var fishService = animalFactory.GetAnimalService("fish");
        var fishes = fishService.SearchForAnimals(new FishSearchOptions {ScalesType = "shiny green"});

        // Search for meows.
        var catService = animalFactory.GetAnimalService("cat");
        var kittyCats = catService.SearchForAnimals(new MamalSearchOptions {HairColour = "golden"});
    }
}

到目前为止一切顺利?请注意,每个服务都可以接受它自己的特定搜索类作为输入参数。例如。 MamalService只能接受MamalSearchOptions类。

这是工厂......

public class AnimalFactory
{
    private IDictionary<string, IAnimalService<IAnimal, IAnimalSearchOptions>> _animals;

    public AnimalFactory()
    {
        _animals = new Dictionary<string, IAnimalService<IAnimal, IAnimalSearchOptions>>
            {
                {"Cat", new MamalService() },
                {"Dog", new MamalService() },
                {"GoldFish", new FishService() }
            };
    }

    public IAnimalService<IAnimal, IAnimalSearchOptions> GetAnimalService(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException("name");
        }

        return _animals[name];
    }
}

这是错误信息:

enter image description here

它不喜欢我试图将服务插入(通用?)字典。

现在,其他类和接口是什么样的?它们可以是found in this gist, here,而不是发送垃圾邮件。 &lt; - 完全100%回购。

有人可以解释我做错了什么并建议我需要做些什么吗?我一直认为,如果超类继承自泛型集合的Type,那么它们总是可以插入到泛型集合中。

1 个答案:

答案 0 :(得分:4)

仅当IAnimalService 协变(例如声明为IAnimalService<out TAnimal, out TSearchOptions>)时才会有效。

不幸的是,根据您的工厂界面,这似乎不起作用。例如,您有:

public class MamalService : IAnimalService<MamalItem, MamalSearchOptions>
{
    public SearchResult<MamalItem> SearchForAnimals(MamalSearchOptions searchOptions)
    {
        throw new NotImplementedException();
    }
}

public class FishService : IAnimalService<Fish, FishSearchOptions>
{
    public SearchResult<Fish> SearchForAnimals(FishSearchOptions searchOptions)
    {
        throw new NotImplementedException();
    }
}

如果编译器允许您的代码,可能会发生以下情况:

var dict = new Dictionary<string, IAnimalService<IAnimal, IAnimalSearchOptions>> {
    { "Dog", new MammalService() }
};

IAnimalService<IAnimal, IAnimalSearchOptions> service = dict["Dog"];

// this would be legal given the type of service, but would clearly not 
// be correct for MammalService:
IAnimal animal = service.SearchForAnimals(new FishSearchOptions());

为了使这个设计起作用,您可以考虑让IAnimalService仅将IAnimalSearchOptions作为其参数而不是通用参数。然后你可以将TAnimal标记为out参数,你就是协变的。当然,您也会失去类型安全性,因为您的MammalService必须将输入IAnimalSearchOptions投射到MammalSearchOptions

另一个常见的解决方案是拥有2个版本的接口,一个是泛型​​的,一个不是(如IEnumerable和IEnumerable),非泛型版本放弃了一些类型安全性,但在上面显示的情况下更有用:

interface IAnimalService { IAnimal SearchForAnimals(IAnimalSearchOptions opts); }

interface IAnimalService<out TAnimal, in TOptions> : IAnimalService
    where TAnimal : IAnimal
    where TOptions : IAnimalSearchOptions {
    TAnimal SearchForAnimals(TOptions opts);
}

class MammalService : IAnimalService<Mammal, MammalSearchOptions>
{

    Mammal SearchForAnimals(MammalSearchOptions opts) { ... }

    IAnimal IAnimalService.SearchForAnimals(IAnimalSearchOptions opts)
    {
        return this.SearchForAnimals((MammalSearchOptions)opts);
    }
}

...

var dict = new Dictionary<string, IAnimalService> { "Dog", new MammalService() };