Unity卡牌游戏泛型类实例的非泛型子类类型检测问题
Unity卡牌游戏泛型类型转换与类型检测问题解决方案
问题根源
你遇到的错误本质是C#泛型的不变性限制:BaseCardAttributes<SpellSets> 无法隐式转换为 BaseCardAttributes<System.Enum>,即使 SpellSets 是 Enum 的派生类型。这导致你无法通过 is 运算符直接判断传入的泛型基类实例是否属于 SpellCardAttributes 子类。
下面提供三种可行的解决方案,适配不同场景需求:
方案一:使用协变泛型接口(推荐用于需要保留泛型类型约束的场景)
通过为接口添加 out 关键字启用协变,允许派生泛型类型转换为基泛型类型,同时解决模板继承的问题。
步骤1:定义协变接口
// 协变接口,out关键字允许TSets的派生类型向基类型转换 public interface ICardAttributes<out TSets> where TSets : System.Enum { string title { get; } string cardText { get; } Sprite cardArt { get; } TSets set { get; } }
步骤2:实现泛型基类
[System.Serializable] public class BaseCardAttributes<TSets> : ICardAttributes<TSets> where TSets : System.Enum { public string title; public string cardText; public Sprite cardArt; public TSets set; // 显式实现接口属性(或直接使用自动属性) string ICardAttributes<TSets>.title => title; string ICardAttributes<TSets>.cardText => cardText; Sprite ICardAttributes<TSets>.cardArt => cardArt; TSets ICardAttributes<TSets>.set => set; }
步骤3:子类与方法实现
// 法术卡牌属性类不变 public class SpellCardAttributes : BaseCardAttributes<SpellSets> { public PositioningType positioning; // 自定义定位枚举 } // 初始化方法使用协变接口作为参数 public void InitializeCard(ICardAttributes<System.Enum> cardAttributes) { title.text = cardAttributes.title; textBox.text = cardAttributes.cardText; cardArt.sprite = cardAttributes.cardArt; // 此时is判断可正常生效 if (cardAttributes is SpellCardAttributes spellCard) { switch (spellCard.positioning) { case PositioningType.Front: // 处理逻辑 break; // 其他分支 } } }
步骤4:修复模板继承
public abstract class BaseCardTemplate<TAttributes> : ScriptableObject where TAttributes : ICardAttributes<System.Enum> { public TAttributes cardAttributes; } public abstract class SpellCardTemplate : BaseCardTemplate<SpellCardAttributes> { // 法术卡牌模板自定义逻辑 }
方案二:非泛型基接口(最简单的类型检测方案)
定义一个非泛型的顶层接口,所有卡牌属性类都实现它,统一用非泛型接口作为方法参数,绕过泛型不变性限制。
步骤1:定义非泛型基接口
public interface ICardAttributes { string title { get; } string cardText { get; } Sprite cardArt { get; } System.Enum set { get; } } // 泛型接口继承非泛型接口,保留类型安全 public interface ICardAttributes<TSets> : ICardAttributes where TSets : System.Enum { new TSets set { get; } }
步骤2:实现泛型基类
[System.Serializable] public class BaseCardAttributes<TSets> : ICardAttributes<TSets> where TSets : System.Enum { public string title; public string cardText; public Sprite cardArt; public TSets set; // 实现非泛型接口 string ICardAttributes.title => title; string ICardAttributes.cardText => cardText; Sprite ICardAttributes.cardArt => cardArt; System.Enum ICardAttributes.set => set; // 实现泛型接口,覆盖set属性 TSets ICardAttributes<TSets>.set => set; }
步骤3:初始化方法与模板
// 初始化方法用非泛型接口作为参数 public void InitializeCard(ICardAttributes cardAttributes) { title.text = cardAttributes.title; textBox.text = cardAttributes.cardText; cardArt.sprite = cardAttributes.cardArt; // is判断直接生效 if (cardAttributes is SpellCardAttributes spellCard) { switch (spellCard.positioning) { // 处理逻辑 } } } // 模板类约束改为非泛型接口 public abstract class BaseCardTemplate<TAttributes> : ScriptableObject where TAttributes : ICardAttributes { public TAttributes cardAttributes; } public abstract class SpellCardTemplate : BaseCardTemplate<SpellCardAttributes> { }
方案三:多态替代类型检测(最符合面向对象设计的方案)
通过多态封装不同卡牌的初始化逻辑,完全避免类型判断,符合开闭原则。
步骤1:定义带初始化方法的接口
public interface ICardAttributes { string title { get; } string cardText { get; } Sprite cardArt { get; } void Initialize(Text titleText, Text textBox, Image cardArtImage); }
步骤2:实现泛型基类与子类
[System.Serializable] public class BaseCardAttributes<TSets> : ICardAttributes where TSets : System.Enum { public string title; public string cardText; public Sprite cardArt; public TSets set; // 基础初始化逻辑 public virtual void Initialize(Text titleText, Text textBox, Image cardArtImage) { titleText.text = title; textBox.text = cardText; cardArtImage.sprite = cardArt; } } public class SpellCardAttributes : BaseCardAttributes<SpellSets> { public PositioningType positioning; // 重写初始化方法,添加法术卡牌自定义逻辑 public override void Initialize(Text titleText, Text textBox, Image cardArtImage) { base.Initialize(titleText, textBox, cardArtImage); switch (positioning) { case PositioningType.Front: // 处理逻辑 break; // 其他分支 } } }
步骤3:简化初始化方法
public void InitializeCard(ICardAttributes cardAttributes) { // 直接调用多态方法,无需类型判断 cardAttributes.Initialize(title, textBox, cardArt); }
内容的提问来源于stack exchange,提问作者Ensaurus
相关产品推荐
相关产品推荐

