【问题标题】:Can't cast inherited class field which is a derived class不能转换作为派生类的继承类字段
【发布时间】:2015-11-19 09:17:22
【问题描述】:

在我解释我的问题之前,请记住,我选择了这样的架构,因为这将在 Unity 下的库存系统中使用,所以我必须将 Item 分开,这是一个 MonoBehavior,它只是来自其数据的世界上的某些东西,这些只是用于库存目的的值......如果这有意义的话。

我的架构是这样的:

public class ItemData
{
    // some fields...
    public string _name; //should be private with its properties but it doesn't matter in the current example
}

public class EquipmentData : ItemData
{
    // some additional fields...
    public float _weight;
}

public class Item
{
    private ItemData _data;

    //Properties
    public virtual ItemData Data { get; set; }
}

public class Equipment : Item
{
    //Properties
    public override ItemData Data 
    {
        get { return _data as EquipmentData; }
        set { _data = value as EquipmentData; }
    }
}

所以,基本上我有一个更深的项目层次结构,但 1 级足以解释我自己。 (它一直像武器:装备一样)......

问题是,如果我将private ItemData _data; 留在Item 类中,并在Equipment 类中添加private EquipmentData _eData;,我将拥有ItemData 字段两次,因为EquipmentData 继承ItemData 等等对于其他派生类...如果我有一个派生自 Equipment 等的类,请第三次获得它...

类似:

public class Item
{
    private ItemData _data;
}

public class Equipment : item
{
    private EquipmentData _eData;
}

ItemData 的字段如_name 将在Equipment 中出现两次,我不希望这样...

所以我猜我的架构有问题,并且可能有一种方法可以绕过这种看起来有点“脏”的方法,但我在网上找不到任何特定于这个问题的东西,我已经达到了我的极限.

我尝试过的:

  • 我曾尝试在Equipment 中使用关键字new,我认为我可以隐藏Item 类中的初始protected ItemData _data;,然后在protected new EquipmentData _data; 中添加protected new EquipmentData _data;,但它显然不会让我,因为对于 Shadow _data 它需要是相同的类型,似乎不适用于派生类型。
  • 另外,如我的代码示例所示,我已尝试覆盖该属性以根据调用它的类返回正确的类型,强制转换始终返回 null...

我仍然觉得奇怪的是,我最终尝试实现类似的东西,所以我乐于接受以更好的方式重组事物的新想法,或者如果有人有我没想过的解决方案来保留那些方式,但让他们工作,这将是非常好的。

我希望我的问题足够详细,如果没有,我可以在需要时解决问题。

【问题讨论】:

  • 您目前的设计有什么问题?为什么private itemdata 而不是protected
  • 因为无论如何我都是通过属性访问它的。我试图覆盖属性以使每个派生类都具有相同的字段,但通过覆盖每个派生类中的属性来从属性中返回该字段的强制转换版本。参考文献我的代码 sn-p 问题是,转换总是返回 null,因为它没有成功将 EquipmentData 转换为 ItemData...
  • 我猜你在被覆盖的道具中做相反的事情,这是行不通的。请参阅下面的回复。

标签: c# inheritance unity3d architecture overriding


【解决方案1】:

您需要的是Generic Classes。这样,您可以为每个Item 分配其正确的ItemData 类型。所以Equipment 将分配其EquipmentData

//TItemData is a name of this generic type. 
//It could be T for example, just like variable name. 
//These type names start from T by convention. 
//This is not a new class or something like that.
//where TItemData : ItemData is a constraint, 
//that assumes that this type should be a subtype of ItemData
public abstract class Item<TItemData> where TItemData : ItemData
{
    protected TItemData Data;
}

//EquipmentData is a subtype of ItemData, so it fits here. 
//No chances to write, for example, IntEquipment : Item<int> , 
//because int does not derive from ItemData.
public class Equipment : Item<EquipmentData>
{
    //here Data will be of EquipmentData type, without any casting.
}

通过实现上述你将实现Type Safety

编辑

要创建一个正确扩展Equipment(我们称其为Weapon)并拥有其正确ItemData(我们称其为WeaponData)的类,您需要编写如下内容:

编辑Equipment,使其成为abstract

public abstract class Equipment<TEquipmentData> 
: Item<TEquipmentData> 
//this constraint is VERY important, as EquipmentData derives from ItemData, thus fulfill Item<TItemData> constraint.
where TEquipmentData : EquipmentData
{
    //Data will have EquipmentData type here.
}

创建WeaponData

public WeaponData : EquipmentData
{
}

创建Weapon

public class Weapon : Equipment<WeaponData>
{
   //Data will have WeaponData type here.
}

【讨论】:

  • 会尝试的。我只是在获取TItemDataItemData 之间的区别时遇到了问题,介意帮我解决一下吗?谢谢!
  • 已编辑,是否澄清了一些事情?
  • 哦,是的,刚刚意识到我的问题有多愚蠢...谢谢,会尝试哦,为了细节,我想我们可以指出 ITemData 应该是 ItemData,不第一个 T 上的大写字母,但它不会改变你的解释,我猜只是一个错字。
  • 好的,这是一个语法问题,但我有一个派生自EquipmentWeapon 类。我试过public class Weapon : Equipment&lt;WeaponData&gt;,这显然不起作用,但我找不到正确的语法。它需要从Equipment 派生而不是直接从Item... 有什么建议吗?
  • 没关系,找到答案here
【解决方案2】:

这行得通:

public class OverridePropertiesWithSameField
{
    public void Test()
    {
        ChildItem ci = new ChildItem();
        ChildItemData cid = new ChildItemData();
        cid.ItemDataProp = "ItemDataProperty"; // Inherited
        cid.ChildItemDataProp = "ChildItemDataProp"; // Specific
        ci.ItemData = cid;

        // You know you need ChildItemData type here.
        var childItemData = ci.ItemData as ChildItemData;
        string itemDataProp = childItemData.ItemDataProp;
        string childItemDataProp = childItemData.ChildItemDataProp;
    }
}

public class Item
{
    protected ItemData data;
    public virtual ItemData ItemData { get; set; }
}

public class ChildItem : Item
{
    public override ItemData ItemData
    {
        get { return base.data; }
        set { base.data = value; }
    }
}

public class ItemData
{
    public string ItemDataProp { get; set; }
}

public class ChildItemData : ItemData
{
    public string ChildItemDataProp { get; set; }
}

【讨论】:

  • 好的,所以我实际上尝试过,并且演员表再次返回 null... 看起来像这样:EquipmentData data = _data as EquipmentData; data.Type = ItemType.EType.Equipment;(其中_dataItem 类中继承的受保护字段)但是当我到达第二行时,我尝试使用 data 中的内容,它是 null... 这是有道理的,因为它不是来自 Item 的正确实例,而是来自当前的父级Equipment(此代码在设备脚本中),但根据我调用这些指令的位置,我仍然看不出有什么出路...
  • 对我来说,在 C# 中不允许将 Base 类型转换为 Derived 类型变量,因此您的示例应该编译但实际上不起作用。你怎么看?
【解决方案3】:

您可以使用带有泛型类型约束(where)的泛型类型参数。

public class Item<DATA> where DATA : ItemData
{
    public virtual DATA Data { get; set; }
}

现在您的班级可以使用特定的 ItemData:

Item<ItemData> has property 
public virtual ItemData Data { get; set; }

Item<EquipmentData> has property
public virtual EquipmentData Data { get; set; }

Item<ANOTHER> has property
public virtual ANOTHER Data { get; set; }

【讨论】:

  • 这有点接近@DreamOnJava 的建议,但在这里你没有考虑到我的类的继承,这就是我在尝试实现这个解决方案时遇到的问题.
  • 在我看来,您实际上并不需要继承该属性,只需更改它的类型即可。类型参数和约束是缺少的成分。
  • 如果通过改变类型你的意思是改变属性的返回类型,我试过了,它不允许我这样做。我还尝试保持相同的返回类型并在 get {} 中转换返回的变量,但遗憾的是,这就是我从...获得null 的地方:x
  • 不,我的意思是使用 Type 参数,以便属性具有您需要的类型。
  • 哦,是的,是的,它看起来确实像 dreamonjava 的答案,我会尝试并让你们知道,谢谢你的时间
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-23
  • 1970-01-01
  • 2014-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多