【问题标题】:C# Generics Inheritance ProblemC#泛型继承问题
【发布时间】:2011-08-20 00:49:34
【问题描述】:

我想将从一个具有泛型的类派生的不同类型的对象添加到基类型列表中。我得到这个编译错误

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

我看不出问题,您能否提供一种替代方法来做这种事情?

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
        }
    }
}

【问题讨论】:

  • 您使用的是什么版本的 .Net?如果是 .Net 4,则需要查看 Covariance。

标签: c# generics inheritance compiler-errors


【解决方案1】:

您可以使用contravariance,但如果您将抽象类更改为接口并将GetAnimals 的返回类型更改为IEnumerable&lt;T&gt;,因为List&lt;T&gt; 不支持这个功能。

有效的代码:

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
    }
}

【讨论】:

  • 嗨@Daniel,感谢您的回答。这很好用,但我现在有一个新问题。当我将方法 void FeedAnimals(IEnumerable&lt;T&gt; animals); 添加到接口 IShelterBase 时,会出现以下编译错误:Invalid variance: The type parameter 'T' must be contravariantly valid on 'IShelterBase&lt;T&gt;.FeedAnimals(IEnumerable&lt;T&gt;)'. 'T' is covariant.
  • 是的,这就是为什么 list 也不支持它的原因。一旦 T 是输入和输出参数,它就不再起作用了。请参阅其他答案中链接的有关该主题的页面。
【解决方案2】:

您可以使用协变和逆变来做到这一点,但是您的 ShelterBase 类需要从接口派生,因为只有接口可以是协变或逆变的。您的列表需要是List&lt;IShelterBase&lt;T&gt;&gt;,它应该可以工作。

See here for more info

【讨论】:

    【解决方案3】:

    要解决这个特定问题,您实际上并不需要协方差。当你消费动物列表时,你仍然通过IShelterBase&lt;out T&gt;接口获得AnimalBase。还不如通过基类公开AnimalBase的列表。

    更好、更简洁的设计是让GetAnimals 返回AnimalBase 的列表,并在每个收容所类中创建一个重载以返回该特定动物的列表。

    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>();}
    }
    

    此设计的另一个问题是通过最派生类型公开集合 - 即 List,而不是 IEnumerableIList。看到您在创建抽象动物集合的类时遇到麻烦,您应该通过禁止直接插入/删除功能来真正保护内部集合。一旦你这样做了,事情就会变得容易一些。例如。以下是我将如何解决这个问题。

    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;}
    }
    

    您会注意到,动物的内部集合从未公开为可变列表。这很好,因为它允许您更严格地控​​制其内容。在这个例子中,基本庇护所中的 AddRemove 方法有点做作,因为它们不会在直接集合访问中添加任何额外的东西,但是您可以在那里添加逻辑 - 例如在添加或删除动物年龄时检查最大庇护所大小。

    【讨论】:

      【解决方案4】:

      一篇关于泛型中的协变和逆变的文章:

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

      【讨论】:

      • 不,这不是协方差的东西。
      • 这篇文章讨论了泛型中的 Co 和 Contra-variance。
      【解决方案5】:

      我刚刚遇到了类似的问题。 我所有的实体都派生自一个基类,我创建了一个方法来返回一个串联的 id 列表。所以用泛型创建了方法并得到了一些转换错误。设法将 generic T 转换为 Object 并将 Object 转换为 BaseClass 并进行某种验证以防万一。

      //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()));
          }
      }
      

      用法:

      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);
      }
      

      编辑: 一段时间后,我需要创建更多层次结构级别,有时还需要转换回父级或祖父级。所以我让我的 CastBack 方法更通用。

      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()));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多