C#genericsinheritance问题

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

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

我看不出问题你能为我提供另一种做这种事情的方法吗?

 abstract class AnimalBase { public int SomeCommonProperty;} abstract class ShelterBase where T : AnimalBase { public abstract List GetAnimals(); public abstract void FeedAnimals(List animals); } class Horse : AnimalBase { } class Stable : ShelterBase { public override List GetAnimals() { return new List(); } public override void FeedAnimals(List animals) { // feed them } } class Duck : AnimalBase { } class HenHouse : ShelterBase { public override List GetAnimals() { return new List(); } public override void FeedAnimals(List animals) { // feed them } } class Program { static void Main(string[] args) { List<ShelterBase> shelters = new List<ShelterBase>(); ///////////////////////////// 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 } } } 

您可以使用逆变 ,但当您将抽象类更改为接口并将GetAnimals的返回类型GetAnimalsIEnumerable ,因为List不支持此function。

有效的代码:

 abstract class AnimalBase { public int SomeCommonProperty;} interface IShelterBase where T : AnimalBase { IEnumerable GetAnimals(); } class Horse : AnimalBase { } class Stable : IShelterBase { public IEnumerable GetAnimals() { return new List(); } } class Duck : AnimalBase { } class HenHouse : IShelterBase { public IEnumerable GetAnimals() { return new List(); } } void Main() { List> shelters = new List>(); shelters.Add(new Stable()); shelters.Add(new HenHouse()); foreach (var shelter in shelters) { var animals = shelter.GetAnimals(); // do something with 'animals' collection } } 

您可以使用协方差和逆变来完成此操作,但您的ShelterBase类需要从接口派生,因为只有接口可以是共变或逆变量。 你的列表需要是一个List> ,它应该工作。

有关详细信息,请参见此处

关于generics的co和contra-variance的文章:

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

要解决这个特殊问题,您实际上并不需要协方差。 当您使用动物列表时,仍然可以通过IShelterBase界面获取AnimalBase 。 不妨通过基类公开AnimalBase列表。

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

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

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

 abstract class ShelterBase where T : AnimalBase { protected List _Animals; public AnimalBase() { _Animals = CreateAnimalCollection(); } protected abstract List CreateAnimalCollection(); public IEnumerable GetAnimals(){return _Animals.Cast();} //Add remove operations go here public void Add(T animal){_Animals.Add(animal);} public void Remove(T animal){_Animals.Remove(animal);} } class Stable : ShelterBase { protected override List CreateAnimalCollection(){return new List();} public IEnumerable GetHorses(){return _Animals;} } 

您会注意到动物的内部集合从未暴露为可变列表。 这很好,因为它允许您更严格地控​​制其内容。 在这个示例中,基本掩体中的AddRemove方法有点人为,因为它们不会在直接集合访问中添加任何额外的东西,但您可以在那里添加逻辑 – 例如,在添加或检查动物年龄之前检查最大的遮蔽大小删除它。

我刚遇到类似的问题。 我的所有实体都派生自一个基类,我创建了一个方法来返回一个连接的id列表。 因此使用generics创建了方法并获得了一些转换错误。 管理将通用T转换Object和Object to BaseClass并进行某种validation以防万一。

 //Needs it generic to use in a lot of derived classes private String ConcatIds(List 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 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 myList) { String ids = this.ConcatIds(myList); } 

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

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