簡體   English   中英

如果對象具有相同的基數 class,如何處理具有不同返回類型的方法?

[英]How to handle methods with different return types if the objects have same base class?

我到處搜索這個,但我沒有找到任何真正幫助我的東西。 我有一個方法,它根據給定的參數返回不同對象的列表(貓的列表或狗的列表):

static **What Goes here?** GetAnimalList(char animalType)
        {
            switch (animalType)
            {
                case 'D':
                    return Dog.GetList();

                case 'C':
                    return Cat.GetList();

                default:
                    Console.WriteLine("Error, here goes an exception");
            }
        }

Dog 和 Cat 都繼承自 Animal。 這兩個類都有自己的 GetList,它生成它們的實例列表。 Animal Abstract Class 中的方法對這兩種動物都適用。

這是三個類:

abstract class Animal
{
    public string Name{ get; set; }
    public string Owner{ get; set; }
}



class Dog : Animal
{
    public string DogThing { get; set; }
   
    public static IList<Dog> GetList()
    {
        using (IDBConnection con = DBConnection.Instance())
        {
            IList<Dog> dogs= new List<Dog>();

            dogs= con.Connect(Properties.Settings.Default.connectionString)
                .Select<Dog>(Properties.Settings.Default.StoredProcedure, CommandType.StoredProcedure);

            return dogs;
        }
    }
}


class Cat: Animal
{
    public string CatThing { get; set; }
       
    public static IList<Cat> GetList()
    {
        using (IDBConnection con = DBConnection.Instance())
        {
            IList<Cat> cats= new List<Cat>();

            cats= con.Connect(Properties.Settings.Default.connectionString)
                    .Select<Cat>(Properties.Settings.Default.StoredProcedure, CommandType.StoredProcedure);

            return cats;
        }
    }
}

如果我將返回類型設置為 IList< Animal > 它會給我錯誤:

無法將類型 Systems.Collections.Generic.IList<Animal.Dog> 隱式轉換為 Systems.Collections.Generic.IList<Animal.Animal>

我試過用 (Animal) 鑄造回報但沒有成功。

代碼的要點是稍后打印出狗的列表或貓的列表及其信息。 理想情況下,我會調用 x.ToString()。 但我被卡住了):這在 C# 中可能嗎? 我覺得這可能是一個設計問題,但我不知道如何解決 go。

編輯:為了簡單起見,我在這個例子中省略了IList並只使用了List ,但原理是一樣的。 如果您願意,絕對可以使用IList

問題

Damien_The_Unbeliever 的評論是正確的。 違反直覺的是,您不能只返回一個List<Dog>就好像它是一個List<Animal>一樣。 您認為可以,因為您可以將Dog作為Animal返回,但協變和逆變會阻止您這樣做。

基本上,請考慮這段代碼。 這是有效的代碼嗎?

var list = new List<Animal>();

list.Add(new Dog());

是的。 那么這個怎么樣?

var list = new List<Cat>();

list.Add(new Dog());

這不是正確的代碼。 Dog不是從Cat派生的,而List<Cat>只能接收Cat對象(或派生對象)。

這個怎么樣?

List<Animal> list = GetAnimals();

list.Add(new Dog());

private List<Animal> GetAnimals() 
{
     return new List<Animal>();
}

這是有效的。 但是這個呢?

List<Animal> list = GetAnimals();

list.Add(new Dog());

private List<Animal> GetAnimals() 
{
     return new List<Cat>();
}

這會中斷,原因與第二個示例中斷的原因相同。 Dog不是從Cat派生的,而List<Cat>只能接收Cat對象(或派生對象)。

這就是為什么您不能僅僅依賴泛型類型的隱式向下轉型,以及為什么您不能像返回List<Animal>一樣返回List<Cat>的原因。 它實際上放寬了類型約束(而不是只能添加Cat對象,現在您可以添加任何Animal ),這違反了 object。


解決方案

您在這里需要做的是將狗或貓添加到List<Animal>而不是將它們添加到List<Dog>List<Cat> 然后你就可以返回那個List<Animal>

有幾種方法可以做到這一點。

將其添加到顯式實例化列表中:

public List<Animal> GetAnimalList(char animalType)
{
    var list = new List<Animal>();

    switch (animalType)
    {
        case 'D':
            list.AddRange(Dog.GetList());

        case 'C':
            list.AddRange(Cat.GetList());

        default:
            Console.WriteLine("Error, here goes an exception");
    }

    return list;
}

使用 LINQ:

public List<Animal> GetAnimalList(char animalType)
{
    switch (animalType)
    {
        case 'D':
            return Dog.GetList().Cast<Animal>().ToList();

        case 'C':
            return Cat.GetList().Cast<Animal>().ToList();

        default:
            Console.WriteLine("Error, here goes an exception");
    }
}

存在其他方法,但它總是歸結為同一件事:將您的Cat / Dog對象添加到List<> ,其通用類型是Animal ,而不是CatDog

您可以使用generics 給你寫了一個沒有連接相關東西的示例程序:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var item in GetAnimalList<Dog>('D'))
            {
                Console.WriteLine(item.DogThing);
            }
        }

        static List<T> GetAnimalList<T>(char animalType)
        {
            switch (animalType)
            {
                case 'D':
                    return Dog.GetList().Cast<T>().ToList();
                case 'C':
                    return Cat.GetList().Cast<T>().ToList();
                default:
                    throw new Exception("unsupported type");
            }
        }

    }
    abstract class Animal
    {
        public string Name { get; set; }
        public string Owner { get; set; }
    }

    class Dog : Animal
    {
        public string DogThing { get; set; }

        public static IList<Dog> GetList()
        {
            IList<Dog> dogs = new List<Dog>() { new Dog() { DogThing = "bowwow" }, new Dog() { DogThing = "wow?" } };
            return dogs;
        }
    }

    class Cat : Animal
    {
        public string CatThing { get; set; }

        public static IList<Cat> GetList()
        {
            IList<Cat> cats = new List<Cat>() { new Cat() { CatThing = "Meow" },new Cat() { CatThing = "Meowy" } };
            return cats;
        }
    }
}

示例版本:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            var animal = new Animal<Dog>() { Name = "Sparky", Owner = "John Doe" };

            Console.WriteLine($"{animal.Name} belongs to {animal.Owner}");
            Console.WriteLine($"{animal.Name} loves to {animal.GetList().First().DogThing} or {animal.GetList().Last().DogThing}");
        }
    }

    interface IAnimals<T>
    {
        string Name { get; set; }
        string Owner { get; set; }
        IList<T> GetList();
    }

    class Animal<T> : IAnimals<T>
    {
        public string Name { get; set; }
        public string Owner { get; set; }

        public IList<T> GetList()
        {
            if (typeof(T) == typeof(Dog))
            {
                //intialize db connection[Not recommended] | call a service with your data[Better approach]
                IList<Dog> dogs = new List<Dog>() { new Dog() { DogThing = "Eat" }, new Dog() { DogThing = "Chew on rug" } };
                return dogs.Cast<T>().ToList();
            }
            else if (typeof(T) == typeof(Cat))
            {
                //intialize db connection[Not recommended] | call a service with your data[Better approach]
                IList<Cat> cats = new List<Cat>() { new Cat() { CatThing = "Hiss" }, new Cat() { CatThing = "Eat fish" } };
                return cats as List<T>;
            }
            else {
                throw new Exception("unsupported type");
            }
        }
    }

    class Dog
    {
        public string DogThing { get; set; }
    }

    class Cat
    {
        public string CatThing { get; set; }
    }    
}

OUTPUT:

在此處輸入圖像描述

您原來的方法不是最優的,這就是為什么您可以看到此線程上的人推薦幾種方法和模式以使其變得更好的原因。

您在這里遇到的問題可以用協方差和逆變來解釋(有關更多信息,請參見msdn )。

假設你有一個List<Dog>的例子,如果你將它轉換/轉換為List<Animal> ,那么這意味着你現在可以選擇調用animalList.Add(new Cat()) ,這將失敗,因為貓不是狗。 要解決此問題,您需要將 go 轉換為不接受 class 的任何實例但僅輸出類型Animal的類型。

如果我們查看List<T>的類型,我們會發現它繼承自某些類,我們只對通用版本感興趣。 如果我們進一步觀察,我們會看到例如ICollection<T>IEnumerable<T> 最后一個( IEnumerable<T> )被聲明為

public interface IEnumerable<out T> 

ICollection<T>沒有提到“out”關鍵字。 這個關鍵字告訴IEnumerable<T>是協變的,因此只會 output 你的類型並且不允許輸入它。 反過來,這可以安全地將您的列表投射到。

另一個選項是IReadOnlyList<T> ,它將為您提供Count屬性和獲取特定索引處元素的選項。

如果你真的想使用List<T> ,那么你唯一的選擇是創建一個新列表,復制所有內容並返回它。 這會導致您可以將狗添加到僅適用於貓的列表中。 您可以按如下方式執行此操作:

return Dog.GetList().ToList<Animal>();

或者

return new List<Animal>(Dog.GetList());

我希望這有助於解決您的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM