C#中泛型接口与继承的实现冲突问题
问题原因
C#泛型接口默认是不变的——即便WordGameItem实现了IWordGameItem,IGame<WordGameItem>和IGame<IWordGameItem>仍被视为两个完全独立的接口。你的WordGame类继承了Game<WordGameItem>(它实现的是IGame<WordGameItem>),同时又实现了IWordGame(它继承自IGame<IWordGameItem>),因此编译器会要求你实现IGame<IWordGameItem>的成员,而基类的实现属于另一个接口,不被编译器认可。
最优解决方案(最小改动)
通过显式实现接口成员,将IWordGame继承的IGame<IWordGameItem>方法转发给基类的已有实现,无需重复编写逻辑:
public class WordGame : Game<WordGameItem>, IWordGame { // 显式实现IGame<IWordGameItem>的Items属性 IReadOnlyList<IWordGameItem> IGame<IWordGameItem>.Items => base.Items; // 显式实现AddGameItem方法,做类型校验后转发给基类 void IGame<IWordGameItem>.AddGameItem(IWordGameItem gameItem) { if (gameItem is not WordGameItem wordItem) { throw new ArgumentException("必须传入WordGameItem类型的实例", nameof(gameItem)); } base.AddGameItem(wordItem); } // 显式实现RemoveGameItem方法,做类型校验后转发给基类 void IGame<IWordGameItem>.RemoveGameItem(IWordGameItem gameItem) { if (gameItem is not WordGameItem wordItem) { throw new ArgumentException("必须传入WordGameItem类型的实例", nameof(gameItem)); } base.RemoveGameItem(wordItem); } }
显式实现的成员仅当WordGame被当作IWordGame或IGame<IWordGameItem>引用时才会被调用,直接使用WordGame实例时仍会调用基类的强类型方法,既满足接口要求,又保证了类型安全。
备选方案(架构调整)
如果需要批量创建类似的游戏类,可以调整基类的泛型约束,让它直接实现目标接口:
// 修改Game基类 public abstract class Game<TBase, TConcrete> : IGame<TBase> where TConcrete : TBase, IGameItem where TBase : IGameItem { public Guid Id { get; set; } = Guid.NewGuid(); protected List<TConcrete> GameItems { get; set; } = new(); public IReadOnlyList<TBase> Items => GameItems.AsReadOnly(); public void AddGameItem(TBase gameItem) { if (gameItem is not TConcrete concreteItem) { throw new ArgumentException($"必须传入{typeof(TConcrete).Name}类型的实例", nameof(gameItem)); } GameItems.Add(concreteItem); } public void RemoveGameItem(TBase gameItem) { if (gameItem is not TConcrete concreteItem) { throw new ArgumentException($"必须传入{typeof(TConcrete).Name}类型的实例", nameof(gameItem)); } GameItems.Remove(concreteItem); } } // WordGame继承时直接满足IWordGame要求 public class WordGame : Game<IWordGameItem, WordGameItem>, IWordGame { // 无需额外实现任何接口成员 }
这个方案通过增加基类的泛型参数,让基类直接实现IGame<TBase>,从而让子类无需额外实现接口,但会增加基类的复杂度,适合多子类场景。
内容的提问来源于stack exchange,提问作者Jaime
相关产品推荐
相关产品推荐

