如何检查一个泛型类实例实际上是一个非泛型子类的实例?

huangapple go评论73阅读模式
英文:

How can I check if a generic class instance is actually an instance of a non-generic child?

问题

I'm working on a card game in Unity right now.

不过,它告诉我"无法处理类型为'BaseCardAttributes<Enum>'的表达式,该表达式无法匹配类型'SpellCardAttributes'的模式"。您是否有关于如何设置此类型检查的建议?我需要在多个地方进行此类型的检查。

我已经尝试将其全部包装在一个接口中,并将其传递到函数中,因为似乎从接口到spellCardAttributes进行转换没有问题。

像这样:

public interface CardAttributes&lt;TSets&gt; where TSets : System.Enum { }

[System.Serializable]
public class BaseCardAttributes&lt;TSets&gt; : CardAttributes&lt;TSets&gt; where TSets : System.Enum

public void InitializeCard(CardAttributes&lt;System.Enum&gt; cardAttributes, CardLogic logic)
{
 if (cardAttributes is SpellCardAttributes spellCardAttributes) {...}
}

然而,这种解决方案在其他地方不起作用。例如,我想为卡片创建不同的模板,如下所示:

public abstract class BaseCardTemplate&lt;TAttributes&gt; : ScriptableObject 
where TAttributes: CardAttributes&lt;System.Enum&gt;

public abstract class SpellCardTemplate : BaseCardTemplate&lt;SpellCardAttributes&gt;

但我无法以这种方式从BaseCardTemplate派生SpellCardTemplate,因为接口和SpellCardAttributes之间的强制转换不起作用。

英文:

I'm working on a card game in Unity right now.

The attributes for cards of different types are very similar, aside from the sets they can belong to, so I have them set up as sibling classes that inherit a generic class which expects an enum for the sets they can be. Like so:

public enum SpellSets
{
    STRESS,
    BOON,
    BUFF
}
public class BaseCardAttributes&lt;TSets&gt; where TSets : System.Enum
{
    *** some fields ***
    public TSets set;
}

public class SpellCardAttributes : BaseCardAttributes&lt;SpellSets&gt; { }

In another class, I want to be able to check if the attributes passed in belong to a spell card or a non spell card, so I am trying to do the following:

public void InitializeCard(BaseCardAttributes&lt;System.Enum&gt; cardAttributes)
    {
        title.text = cardAttributes.title;
        textBox.text = cardAttributes.cardText;
        cardArt.sprite = cardAttributes.cardArt;
        if (cardAttributes is SpellCardAttributes spellCardAttributes)
            {
                switch (spellCardAttributes.positioning)
                {
                    *** switch stuff ***
                }
            }
    }

But it's telling me "An expression of type 'BaseCardAttributes<Enum>' cannot be handled by a pattern of type 'SpellCardAttributes'". Any advice I how I could setup this sort of type checking? I will need to check this sort of thing multiple places.

I've tried wrapping it all in an interface, and passing that in to the function instead, since it seems to have no problem casting from the interface to spellCardAttributes.

Like so:

public interface CardAttributes&lt;TSets&gt; where TSets : System.Enum { }

[System.Serializable]
public class BaseCardAttributes&lt;TSets&gt; : CardAttributes&lt;TSets&gt; where TSets : System.Enum

public void InitializeCard(CardAttributes&lt;System.Enum&gt; cardAttributes, CardLogic logic)
{
 if (cardAttributes is SpellCardAttributes spellCardAttributes) {...}
}

However, that solution doesn't work other places. Like, I would like to have different templates I can fill in for the cards like so:

public abstract class BaseCardTemplate&lt;TAttributes&gt; : ScriptableObject 
where TAttributes: CardAttributes&lt;System.Enum&gt;

public abstract class SpellCardTemplate : BaseCardTemplate&lt;SpellCardAttributes&gt;

But I can't derive SpellCardTemplate from BaseCardTemplate that way because that cast between the interface and SpellCardAttributes doesn't work.

答案1

得分: 2

以下是您要翻译的内容:

"核心问题在于 enum 是值类型,而值类型不支持继承... 除非在极其有限的情况下支持,比如 System.Enum -> enum。作为基本上唯一从除了 System.Object 之外的其他东西继承的值类型,enum 非常特殊。这是编译器真的不擅长处理的事实。

直到 C# 7.3,不可能将 System.Enum 用作通用约束,因为值类型通常情况下不能从其他值类型继承。今天,至少可以将其用作约束,但编译器嘲笑任何东西可以从值类型如 System.Enum 派生,所以不会允许你做任何类似的事情。

幸运的是,这并不是您代码的结局。虽然我们不能像您想要的那样处理专门的类,但还有其他选项。

非通用基类

第一个选项是插入一个非通用的更高级基类,或者至少只受到类类型的约束。让我们保持简单,使用一个普通的基类:

public abstract class BaseCardAttributes 
{
    // 在此添加您的通用非通用属性:
    public string Title { get; protected set; }
    public string CardText { get; protected set; }
    public Image CardArt { get; protected set; }
}

public class BaseCardAttributes&lt;TSets&gt; : BaseCardAttributes
    where TSets : System.Enum
{
    public TSets set;
}

public void InitializeCard(BaseCardAttributes cardAttributes)
{
    // ...
    if (cardAttributes is SpellCardAttributes spellCardAttributes)
    {
        // 在这里执行任何操作。
    }
}

或者,您可以拥有一个非通用的接口,涵盖大部分属性:

public interface IBaseCardAttributes
{
    string Title { get; }
    string CardText { get; }
    Image CardArt { get; }
}

public class BaseCardAttributes&lt;TSets&gt; : IBaseCardAttributes
    where TSets : System.Enum
{
    // ...
}

public void InitializeCard(IBaseCardAttributes cardAttributes)
{
    // 与上面的代码相同,稍微不同的方法签名
}

无论哪种方式都可以从 InitializeCard 方法中移除通用约束,同时仍然实现您想要的功能。但它们可能比必要的工作要多一些。

泛化方法

简而言之,现在您已经阅读了我的长篇大论,可以将 InitializeCard 方法泛化:

public void InitializeCard&lt;TSets&gt;(BaseCardAttributes&lt;TSets&gt; cardAttributes)
    where TSets : System.Enum
{
    // 您上面的代码现在可以工作。
}

我无法看到您的程序架构的其余部分,但构建一个必须支持所有可能的卡片类型的 InitializeCard 方法似乎有点笨拙。您考虑过将初始化逻辑移到 BaseCardAttribute&lt;&gt; 实现中吗?有时将紧密相关的逻辑和数据放在一起更好。"

英文:

The core problem here is that enums are value types, and value types do not support inheritence... except in the very narrow scope of when they do, like System.Enum -> enum. As basically the only value type that inherits from something other than System.Object, enums are very special. A fact which the compiler really isn't good at dealing with.

Up until C# 7.3 it wasn't possible to use System.Enum as a generic constraint, since value types cannot (under basically any other circumstance) inherit from other value types. Today we can at least use it as a constraint, but the compiler scoffs at the idea that anything could be derived from a value type like System.Enum so it won't let you do anything like that.

Fortunately it's not the end of the story for your code. While we can't treat the specialized classes the way you want, there are options.

Non-Generic Base

The first option is to insert a higher base class that isn't generic, or at least is only constrained to class types. Let's keep it simple and go with a plain old base class:

public abstract class BaseCardAttributes 
{
    // add your common non-generic properties here:
    public string Title { get; protected set; }
    public string CardText { get; protected set; }
    public Image CardArt { get; protected set; }
}

public class BaseCardAttributes&lt;TSets&gt; : BaseCardAttributes
    where TSets : System.Enum
{
    public TSets set;
}

public void InitializeCard(BaseCardAttributes cardAttributes)
{
    // ...
    if (cardAttributes is SpellCardAttributes spellCardAttributes)
    {
        // do whatever here.
    }
}

Alternatively you can have a non-generic interface that covers the bulk of the properties instead:

public interface IBaseCardAttributes
{
    string Title { get; }
    string CardText { get; }
    Image CardArt { get; }
}

public class BaseCardAttributes&lt;TSets&gt; : IBaseCardAttributes
    where TSets : System.Enum
{
    // ...
}

public void InitializeCard(IBaseCardAttributes cardAttributes)
{
    // same code as above, slightly different method signature
}

Either way removes the generics from the InitializeCard method while still doing what you want. But they're a bit more work than necessary.

Generalize the method

The short answer - now that you've sat through my long one - is to generalize the InitializeCard method:

public void InitializeCard&lt;TSets&gt;(BaseCardAttributes&lt;TSets&gt; cardAttributes)
    where TSets : System.Enum
{
    // Your code above now works.
}

I can't see the rest of your program architecture, but it seems a little clumsy to be building an InitializeCard method that has to support all of the possible card types. Have you considered moving the initialization logic to the BaseCardAttribute&lt;&gt; implementations? Sometimes it's better to keep tightly-related logic and data together.

huangapple
  • 本文由 发表于 2023年5月26日 08:04:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76336893.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定