英文:
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<TSets> where TSets : System.Enum { }
[System.Serializable]
public class BaseCardAttributes<TSets> : CardAttributes<TSets> where TSets : System.Enum
public void InitializeCard(CardAttributes<System.Enum> cardAttributes, CardLogic logic)
{
if (cardAttributes is SpellCardAttributes spellCardAttributes) {...}
}
然而,这种解决方案在其他地方不起作用。例如,我想为卡片创建不同的模板,如下所示:
public abstract class BaseCardTemplate<TAttributes> : ScriptableObject
where TAttributes: CardAttributes<System.Enum>
public abstract class SpellCardTemplate : BaseCardTemplate<SpellCardAttributes>
但我无法以这种方式从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<TSets> where TSets : System.Enum
{
*** some fields ***
public TSets set;
}
public class SpellCardAttributes : BaseCardAttributes<SpellSets> { }
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<System.Enum> 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<TSets> where TSets : System.Enum { }
[System.Serializable]
public class BaseCardAttributes<TSets> : CardAttributes<TSets> where TSets : System.Enum
public void InitializeCard(CardAttributes<System.Enum> 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<TAttributes> : ScriptableObject
where TAttributes: CardAttributes<System.Enum>
public abstract class SpellCardTemplate : BaseCardTemplate<SpellCardAttributes>
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<TSets> : 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<TSets> : IBaseCardAttributes
where TSets : System.Enum
{
// ...
}
public void InitializeCard(IBaseCardAttributes cardAttributes)
{
// 与上面的代码相同,稍微不同的方法签名
}
无论哪种方式都可以从 InitializeCard
方法中移除通用约束,同时仍然实现您想要的功能。但它们可能比必要的工作要多一些。
泛化方法
简而言之,现在您已经阅读了我的长篇大论,可以将 InitializeCard
方法泛化:
public void InitializeCard<TSets>(BaseCardAttributes<TSets> cardAttributes)
where TSets : System.Enum
{
// 您上面的代码现在可以工作。
}
我无法看到您的程序架构的其余部分,但构建一个必须支持所有可能的卡片类型的 InitializeCard
方法似乎有点笨拙。您考虑过将初始化逻辑移到 BaseCardAttribute<>
实现中吗?有时将紧密相关的逻辑和数据放在一起更好。"
英文:
The core problem here is that enum
s 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
, enum
s 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<TSets> : 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<TSets> : 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<TSets>(BaseCardAttributes<TSets> 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<>
implementations? Sometimes it's better to keep tightly-related logic and data together.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论