You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 22:54:55