C# Generics Inheritance Problem

前端 未结 5 535
囚心锁ツ
囚心锁ツ 2020-12-09 19:24

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: c         


        
相关标签:
5条回答
  • 2020-12-09 19:40

    An article on co and contra-variance in generics:

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

    0 讨论(0)
  • 2020-12-09 19:51

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-09 19:52

    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.

    0 讨论(0)
  • 2020-12-09 19:52

    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()));
        }
    }
    
    0 讨论(0)
  • 2020-12-09 20:03

    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

    0 讨论(0)
提交回复
热议问题