C# Generics Inheritance Problem

萝らか妹 提交于 2019-11-27 02:43:52

问题


I'd like to add different types of objects derived from one class with generics into a List of base type. I get this compile error

Error   2   Argument 1: cannot convert from 'ConsoleApplication1.Stable' to 'ConsoleApplication1.ShelterBase<ConsoleApplication1.AnimalBase>'   C:\Users\ysn\Desktop\ConsoleApplication1\ConsoleApplication1\Program.cs 43  26  ConsoleApplication1

I can't see the problem could you provide me an alternative way of doing this kind of thing?

abstract class AnimalBase { public int SomeCommonProperty;}

abstract class ShelterBase<T> where T : AnimalBase
{
    public abstract List<T> GetAnimals();
    public abstract void FeedAnimals(List<T> animals);
}


class Horse : AnimalBase { }

class Stable : ShelterBase<Horse>
{
    public override List<Horse> GetAnimals()
    {
        return new List<Horse>();
    }

    public override void FeedAnimals(List<Horse> animals)
    {
        // feed them
    }
}


class Duck : AnimalBase { }

class HenHouse : ShelterBase<Duck>
{
    public override List<Duck> GetAnimals()
    {
        return new List<Duck>();
    }

    public override void FeedAnimals(List<Duck> animals)
    {
        // feed them
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<ShelterBase<AnimalBase>> shelters = new List<ShelterBase<AnimalBase>>();

        ///////////////////////////// following two lines do not compile
        shelters.Add(new Stable()); 
        shelters.Add(new HenHouse());
        /////////////////////////////

        foreach (var shelter in shelters)
        {
            var animals = shelter.GetAnimals();
            // do sth with 'animals' collection
        }
    }
}

回答1:


You can use contravariance, but only if you change your abstract class to an interface and the return type of GetAnimals to an IEnumerable<T>, because List<T> doesn't support this feature.

Code that works:

abstract class AnimalBase { public int SomeCommonProperty;}

interface IShelterBase<out T> where T : AnimalBase
{
    IEnumerable<T> GetAnimals();
}

class Horse : AnimalBase { }

class Stable : IShelterBase<Horse>
{
    public IEnumerable<Horse> GetAnimals()
    {
        return new List<Horse>();
    }
}

class Duck : AnimalBase { }

class HenHouse : IShelterBase<Duck>
{
    public IEnumerable<Duck> GetAnimals()
    {
        return new List<Duck>();
    }
}

void Main()
{
    List<IShelterBase<AnimalBase>> shelters = new List<IShelterBase<AnimalBase>>();

    shelters.Add(new Stable());
    shelters.Add(new HenHouse());

    foreach (var shelter in shelters)
    {
        var animals = shelter.GetAnimals();
        // do something with 'animals' collection
    }
}



回答2:


You can do this using covariance and contravariance, but your ShelterBase class needs to derive from an interface, since only interfaces can be co- or contravariant. You list will need to be a List<IShelterBase<T>> and it should work.

See here for more info




回答3:


An article on co and contra-variance in generics:

http://msdn.microsoft.com/en-us/library/dd799517.aspx




回答4:


To solve this particular problem you don't actually need covariance. When you consume animal lists, you still get AnimalBase through IShelterBase<out T> interface. Might as well expose a list of AnimalBase through the base class.

A better and cleaner design would be to make GetAnimals return a list of AnimalBase, and to create an overload in each shelter class to return a list of that particular animal.

abstract class ShelterBase<T> where T : AnimalBase
{
    public List<AnimalBase> GetAnimals(){return new List<AnimalBase>();}
}

class Stable : ShelterBase<Horse>
{
    public List<Horse> GetHorses(){return new List<Horse>();}
}

Another problem with this design is exposing a collection through the most derived type - i.e. List, as opposed to IEnumerable or IList. Seeing as you went into trouble of creating a class abstracting a collection of animals, you should really protect the inner collection by disallowing direct insert/delete ability. Once you do that, things become a bit easier. E.g. here is how I would solve this problem.

abstract class ShelterBase<T> where T : AnimalBase
{
    protected List<T> _Animals;
    public AnimalBase() {
       _Animals = CreateAnimalCollection();
    }

    protected abstract List<T> CreateAnimalCollection();
    public IEnumerable<AnimalBase> GetAnimals(){return _Animals.Cast<AnimalBase>();}

    //Add remove operations go here
    public void Add(T animal){_Animals.Add(animal);}
    public void Remove(T animal){_Animals.Remove(animal);}

}

class Stable : ShelterBase<Horse>
{
    protected override List<Horse> CreateAnimalCollection(){return new List<Horse>();}

    public IEnumerable<Horse> GetHorses(){return _Animals;}
}

You will notice that the internal collection of animals is never exposed as mutable list. This is good, as it allows you tighter control over its contents. The Add and Remove methods in the base shelter are a bit contrived in this example as they don't add anything extra over direct collection access, but you can add logic there - e.g. checking for maximum shelter size when adding or checking the animal age before removing it.




回答5:


I just got a similar problem. All my entities derive from a base class and I created a method to return a concatenated list of ids. So created the method with generics and got some convert errors. Managed to cast the generic T to Object and Object to BaseClass and put some sort of validation just in case.

//Needs it generic to use in a lot of derived classes
private String ConcatIds<T>(List<T> listObj)
{
    String ids = String.Empty;

    foreach (T obj in listObj)
    {
        BaseEntity be = CastBack(obj);

        if (ids.Count() > 0)
            ids = ids + String.Format(", {0}", be.Id);
        else
            ids = be.Id.ToString();
    }

    return ids;
}

//I'll probably move it to the Base Class itself
private BaseEntity CastBack<T>(T genericObj)
{
    Type type = typeof(T);

    if (type.BaseType == typeof(BaseEntity))
    {
        Object _obj = (Object)genericObj;
        return (BaseEntity)_obj;
    }
    else
    {
        throw new InvalidOperationException(String.Format("Cannot convert {0} to BaseEntity", type.ToString()));
    }
}

usage:

public class BaseEntity
{
   public Int32 Id {get; set;}
}

public class AnyDerivedClass : BaseEntity
{
  // Lorem Ipsum
}

private void DoAnything(List<AnyDerivedClass> myList)
{
    String ids = this.ConcatIds<AnyDerivedClass>(myList);
}

Edit: After a while I needed to create more hierarchy level and sometimes cast back to parent or to grand parent. So I let my CastBack method more generic.

private B CastBack<T,B>(T genericObj)
{
    Type type = typeof(T);

    if (type.BaseType == typeof(B))
    {
        Object _obj = (Object)genericObj;
        return (B)_obj;
    }
    else
    {
        throw new InvalidOperationException(String.Format("Cannot cast back {0}", type.ToString()));
    }
}


来源:https://stackoverflow.com/questions/5909209/c-sharp-generics-inheritance-problem

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!