C# 泛型继承问题
本文关键字:问题 继承 泛型 | 更新日期: 2023-09-27 17:55:19
我想将从一个类派生的不同类型的对象添加到基类型列表中。我收到此编译错误
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
}
}
}
您可以使用逆变,但前提是您将抽象类更改为接口并将返回类型GetAnimals
更改为IEnumerable<T>
,因为List<T>
不支持此功能。
有效的代码:
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
}
}
您可以使用协方差和逆变来执行此操作,但您的 ShelterBase 类需要从接口派生,因为只有接口可以是协变或逆变。 您的列表需要是一个List<IShelterBase<T>>
并且应该可以工作。
更多信息请看这里
要解决这个特定问题,您实际上不需要协方差。当您使用动物列表时,您仍然可以通过IShelterBase<out T>
界面获得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
,而不是IEnumerable
或IList
。看到您在创建抽象动物集合的类时遇到了麻烦,您应该通过禁止直接插入/删除功能来真正保护内部集合。一旦你这样做了,事情就会变得容易一些。例如,这是我如何解决这个问题。
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;}
}
您会注意到,动物的内部集合永远不会作为可变列表公开。这很好,因为它可以让您更严格地控制其内容。在此示例中,基本庇护所中的Add
和Remove
方法有点做作,因为它们不会通过直接收集访问添加任何额外的内容,但您可以在那里添加逻辑 - 例如,在添加或检查动物年龄时检查最大庇护所大小在删除它之前。
一篇关于泛型中的co和逆方差的文章:
http://msdn.microsoft.com/en-us/library/dd799517.aspx
我刚刚遇到了类似的问题。我所有的实体都派生自基类,我创建了一个方法来返回 id 的串联列表。所以用泛型创建了方法,并得到了一些转换错误。设法将通用 T 强制转换为对象,将对象强制转换为 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()));
}
}