当派生对象存储在父类型集合中时,如何访问它们的属性

本文关键字:访问 何访问 属性 存储 对象 派生 父类 类型 集合 | 更新日期: 2023-09-27 18:22:21

我在RPG上工作,我一直在研究如何制作世界上所有物品的全局列表。我在Unity3D工作。

我的物品可以是武器、货币、macscots和十几种派生类。

所以我有一个基本类项目(简化代码)

public class Item{
    public int Id {get;set;}
    public Vector2 GeoCoord{get;set;}
    public int OwnerId{get;set;}
    public string State{get;set;}
    public string Name{get;set;}
    public Type Type{get;set;}
}

和子类

public class WeaponModel: Item {
    public Type CloseTargetBehavior{get;set;}
    public Type DistantTargetBehavior{get;set;}
    ...
}
public class MascotModel: Item {
    public MascotCategory Category {get; set;}
    ...
}

public class ConsumableModel: Item {
    public float Price {get; set;}
    ...    
}

出于保存目的和其他原因,我希望将这些派生类的所有实例存储在字典(或其他任何东西)中。但我读到,不建议将多类型对象存储在集合中,因为您只能访问它们的公共属性(此处为Item的类属性)。

那么,什么是拥有所有类型对象的全局列表并仍然可以访问其属性的最佳方法呢?

第1版:添加了一些关于使用的详细信息

我对这个集合的第一个用途是用它来描述一些"工厂":

1) 当游戏开始时,我反序列化一个JSON文件,该文件构建了这个独特的集合。我使用http://www.dustinhorne.com/post/Json-NET-for-Unity-Developers我使用设置。TypeNameHandling=TypeNameHandling.All;参数以保留所有类/子类的描述。

2) 不同的具体游戏对象,可以被同化为视图元素,用于二维地图、三维世界、库存。。。但每个表示都引用了子类实例(WeaponModel或MascotModel或…)。例如:地图上或3D视图中项目上的clic都会改变模型的状态。

我想"if(typeOf(WeaponModel))else if…"但这是我困惑的起点。我正在寻找一种更通用的方法来做这件事,因为我不知道我会有多少子类。

我尝试了一些动态铸造,但我读到这在C#中是不可能的。这是我的尝试:

Type type = Items[key].GetType();
Debug.Log(type()); //WeaponModel but I have no access to the method/property
Item item = (type) Items[key]; // Cast don't work
Debug.Log(item.CloseTargetBehavior) // Still no access to subclass property
Item item = (WeaponModel) Items[key]; // Cast works because it is a declarative type
Debug.Log(item.CloseTargetBehavior) // I have ccess to subclass property

当派生对象存储在父类型集合中时,如何访问它们的属性

我认为这里真正的问题是用于离线存储数据的序列化器/反序列化器。无论做出什么选择,反序列化程序都必须具有类型意识,以便在导入保存数据时重新创建正确类型的对象。.Net XML和二进制序列化程序都存储详细的类型信息并反序列化为正确的类型。

如果用于保存所有数据的主容器(序列化/反序列化的根对象)具有List<gt;对于要存储的每一个派生类型,您都可以获得对存储数据的类型化访问,而无需经历太多困难。我根本不认为使用字典有什么意义,因为您将项和所有者ID存储在item基类中,并且可以在反序列化后扫描列表,将项重新附加到其关联的所有者。

您可能唯一想要字典的是创建一个ItemContainer对象,该对象具有用于添加项的通用/通用方法,例如:Container.AddItem<武器模型>(武器型号项目);或基于派生类型访问列表Container.GetList<武器模型>()Container.GetItemByID<武器模型>(int id)这样就不必显式地创建一个包含所有类型列表的容器类。。使用这些方法简单地添加派生对象就会创建派生类型,并以透明的方式从中添加/删除/访问数据,并且它仍然可以正确地与串行器/解串器一起工作。

EDIT:根据要求,提供一个通用技术的快速示例,用于存储从以强类型方式访问的Item派生的任何类型的数据:

使用系统;使用System.Collections.Generic;公共类项目{公共int Id{get;set;}public Vector2 GeoCoord{get;set;}public int OwnerId{get;set;}公共字符串状态{get;set;}公共字符串名称{get;set;}public Type类型{get;set;}}公共类ItemContainer{私人词典<类型,对象>项目;public ItemContainer(){items=新词典<类型,对象>();}公共T获取<T>(int id)其中T:项{var t=(t)的类型;if(!items.ContainsKey(t))返回null;var dict=items[t]作为Dictionary<int,T>;if(!dict.ContainsKey(id))返回null;return(T)dict[id];}公共无效集<T>(T项目)其中T:项目{var t=(t)的类型;if(!items.ContainsKey(t)){items[t]=新字典<int,T>();}var dict=items[t]作为Dictionary<int,T>;dict[item.Id]=项目;}公共词典<int,T>获取全部<T>()其中T:项目{var t=(t)的类型;if(!items.ContainsKey(t))返回null;将items[t]返回为Dictionary<int,T>;}}

问题是要求所有项目的单个全局列表,因此这将完全处理这种情况。

储存物品:

public static List<Item> GlobalItems = new List<Item>();

为了访问派生类型的属性,必须将Item强制转换为其派生类型。

要确定它的当前类型,请使用:

var itemType = item.GetType();

使用项目的派生类型:

Item item = GlobalItems[0];
DerivedItem derivedItem = item as DerivedItem;
if(derivedItem == null)
{
    Console.WriteLine("Item is not a DerivedItem");
}

要从列表中获取特定类型的所有项目:

var derivedItems = GlobalItems.Where(item => item.GetType() == typeof(DerivedItem)).Select(item => (DerivedItem)item);

为了给Ascension的答案增加一种可能性(谁写出来的所有功劳),你也可以添加方法:

    public Dictionary<int, T> GetAll<T>() where T: Item
    {
        var t = typeof(T);
        if (!items.ContainsKey(t)) return null;
        var derivedItemDict = new Dictionary<int,T>();
        foreach(var item in items[t]) {
            derivedItemDict.Add(item.Key, (T)item.Value);
        }
        return derivedItemDict;
    }

然后你可以,例如,做:

Items.GetAll<Weapon>();

为您返回Dictionary<int, Weapon>。。也就是说,它会还给你我相信你在找的强类型词典。但请注意,如果您经常使用它,那么在更改之间应该单独保存它的结果,这样您就不必重复执行所有这些强制转换。