我如何使用泛型将这个非常简单的 .NET 代码修复为 return 强类型结果?

How can I fix this really simple .NET code using Generics to return the strongly typed result?

注意:下面的所有代码 exists in this .NET Fiddle

我(再次)在尝试理解 Generics/covariance/contravariance 时遇到了问题。 :(

这太让我难受了-所以请善待我。

所以,我有这家虚构的宠物店,我正在尝试轻松 在我们的宠物库存中搜索宠物

主要搜索 class SearchMagic 是我所有的 greif 所在。我想说的是:

var pet = searchMagic.SearchForAPet("Fido"); 它会..在这种情况下 return 一个 Dog 这是 Fido.

现在最重要的是我如何实现 class SearchMagic。这是它首先搜索 Cat,然后是 Dog 的聪明之处。问题是,如果找到的话,我似乎无法找到 return 猫或狗实例的方法。它一直抱怨Cannot implicitly convert...

我一直试图对代码说:至少,我需要一个 SearchResult<IAnimal> returned。因此,如果那是具体的 CatDog,那就太棒了。否则为 null。然后对于 调用 该方法并获得 returned 结果的代码,我唯一可以 的结果,是 SearchResult<IAnimal> 中定义的任何内容。因此,如果我的 Cat 实例有一些 cat 特定的属性或方法,那么运气不好,我不能在 result 上调用它们,因为结果是 SearchResult<IAnimal> 而只有 IAnimal 东西暴露了。

这是代码,内联一些注释以显示错误发生的位置...

请告诉我 ze 代码。

using System;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");

        var searchMagic = new SearchMagic();

        var animal = searchMagic.SearchForAPet("Fido");
    }


    // ***********************************
    // ** Interfaces / contracts        **
    // ***********************************


    public interface IAnimal
    {
        string Name { get; set; }
    }

    public interface ISearchService<TAnimal> where TAnimal : IAnimal
    {
        SearchResult<TAnimal> SearchInventory(string query);
    }

    public class SearchResult<TAnimal> where TAnimal : IAnimal
    {
        TAnimal Pet { get; set; }
        decimal Price { get; set; }
    }


    // ***********************************
    // ** Concrete implementations      **
    // ***********************************

    public class Cat : IAnimal
    {
        public string Name { get; set; }
    }

    public class Dog : IAnimal
    {
        public string Name { get; set; }
    }

    public class CatSearchService : ISearchService<Cat>
    {
        public SearchResult<Cat> SearchInventory(string query)
        {
            return new SearchResult<Cat>();
        }
    }

    public class DogSearchService : ISearchService<Dog>
    {
        public SearchResult<Dog> SearchInventory(string query)
        {
            return new SearchResult<Dog>();
        }
    }

    public class SearchMagic
    {
        private CatSearchService _catSearchService = new CatSearchService();
        private DogSearchService _dogSearchService = new DogSearchService();

        public SearchResult<IAnimal> SearchForAPet(string query)
        {
            var cat = _catSearchService.SearchInventory(query);
            if (cat != null)
            {
                // ** ERROR: Cannot implicitly convert type 'Program.SearchResult<Program.Cat>' to 'Program.SearchResult<Program.IAnimal>'
                return cat;
            }

            var dog = _dogSearchService.SearchInventory(query);
            if (dog != null)
            {
                // ** ERROR: Cannot implicitly convert type 'Program.SearchResult<Program.Dog>' to 'Program.SearchResult<Program.IAnimal>'  
                return dog;
            }

            return null;
        }
    }

}

Here's one way of doing it.

如果您希望 SearchForAPet 方法能够 return 可以是 "dog search result" 或 "cat search result" 的 "search result",它需要return 协变接口,而不是具体类型:

public interface ISearchResult<out TAnimal> where TAnimal: IAnimal
{
    TAnimal Pet { get; }
    decimal Price { get; }
}

public class SearchResult<TAnimal> : ISearchResult<TAnimal> where TAnimal : IAnimal
{
    public TAnimal Pet { get; set; }
    public decimal Price { get; set; }
}

...

public ISearchResult<IAnimal> SearchForAPet(string query)
{
    ...
}

也没有找到任何其他方法来保持结构完整。您需要类似以下内容才能从中获取搜索结果:

public interface ISearchResult<out TAnimal> where TAnimal : IAnimal
{
    TAnimal Pet { get; }
    decimal Price { get; }
}

在任何情况下,如果您想选择退出 out,您需要在执行 return 时相应地进行转换。

原因是:

这里看起来像是另一个经典的协变、逆变和不变问题。根据 doc here,您可以使用协变,这将使您能够使用比最初指定的派生类型更多的类型,反之亦然,但您不可能期望这样做:

public class Program
{      
    public static void Main()
    {
        List<Derived> dlist = new List<Derived>();
        List<Base> bIEnum = dlist;
    }
}

但是你可以:

public class Program
{  
    public static void Main()
    {
        List<Derived> dlist = new List<Derived>();
        IEnumerable<Base> bIEnum = dlist;
    }
}

因此,拥有 ISearchResult 或 ISearchResult 是您最好的选择