dagatsoin
dagatsoin

Reputation: 2656

How to access derived objects properties when they are stored in a Parent typed collection?

I works on a RPG and I am stuck on how to make a global list of all items of the world. I works with Unity3D.

My items could be weapons, currency, macscots and dozen of subderived class.

So I have a base Class Item (simplified code)

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;}
}

and sub classes

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;}
    ...    
}

For saving purpose and others reasons, I would like to store all the instances of those derived class in a dictionary (or anything else). But I read it is not recommended to store multi typed objects in a collection because you can only access their common properties (here Item's class properties).

So what is the best way to have a global list of all sort of objects and still get access to their properties?

EDIT 1 : Added some details on usage

My first use for this collection is to use it as a description for some "factories":

1)When the game starts, I deserialize a JSON files which build this unique collection. I use http://www.dustinhorne.com/post/Json-NET-for-Unity-Developers I use a settings.TypeNameHandling = TypeNameHandling.All; parameter to keep all class/subclasses description.

2)Different concrete GameObject, which could be assimilated as a View element, are made to use in 2Dmap, 3Dworld, inventory... but each representation refers to the subclass instance (WeaponModel or MascotModel or...). Eg : a clic on an item on the map, or in the 3D view will both change the state of the Model.

I thought about "if(typeOf(WeaponModel)) else if..." But this is the start point of my confusion. I am looking a more generic way to do this because I don't know how many subclass I will have.

I tried some dynamic casting, but I read it is not possible in C#. This is my try :

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

Upvotes: 0

Views: 1081

Answers (3)

Ascendion
Ascendion

Reputation: 169

I believe the real issue here is the serializer/deserializer used to store the data offline. Whatever choice is made, the deserializer has to be Type aware so it recreates objects of the correct type when the save data is imported. The .Net XML and Binary serializers both store detailed type information and deserialize to the correct types.

If your primary container (the root object that you serialize/deserialize) for saving all the data had a List<> for each derived type that you wanted to store, you would get typed access to the stored data without jumping through a lot of hoops. I don't see the point of using a dictionary at all since you store item and owner IDs in the Item base class and you can scan the lists after deserializing to reattach the items to their associated owners.

The only thing you MIGHT want a dictionary for is to create an ItemContainer 
object with a common/generic method for adding items like: 

Container.AddItem<WeaponModel>(WeaponModel item); 

or accessing the lists based on the derived type 

Container.GetList<WeaponModel>()
Container.GetItemByID<WeaponModel>(int id)

so that you don't have to explicitly create a container class with all the typed lists.. 

simply adding a derived object using these methods would create a list of the 
derived type and add/remove/access data from it in a transparent manner, 
and it would still work correctly with the serializer/deserializer.

EDIT: as requested a quick example of a generic technique to store data of any type derived from Item that is accessible in strongly typed fashion:


    using System;
    using System.Collections.Generic;
    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 ItemContainer
    {
        private Dictionary<Type, object> items;

        public ItemContainer()
        {
            items = new Dictionary<Type, object>();
        }

        public T Get<T>(int id) where T: Item
        {
            var t = typeof(T);
            if (!items.ContainsKey(t)) return null;
            var dict = items[t] as Dictionary<int, T>;
            if (!dict.ContainsKey(id)) return null;
            return (T)dict[id];
        }

        public void Set<T>(T item) where T: Item
        {
            var t = typeof(T);
            if (!items.ContainsKey(t))
            {
                items[t] = new Dictionary<int, T>();
            }
            var dict = items[t] as Dictionary<int, T>;
            dict[item.Id] = item;
        }

        public Dictionary<int, T> GetAll<T>() where T : Item
        {
            var t = typeof(T);
            if (!items.ContainsKey(t)) return null;
            return items[t] as Dictionary<int, T>;
        }
    }

Upvotes: 1

David E
David E

Reputation: 1444

Just to add a possibility to Ascendion's answer (who gets all the credit for writing it up), you could also add the method:

    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;
    }

And then you could, eg, do:

Items.GetAll<Weapon>();

to return a Dictionary<int, Weapon> for you.. Ie it would return you the strongly-typed dictionary I believe you seek. But note that if you use this a lot, the result of this should really be saved separately between changes so you don't have to repetitively do all these casts.

Upvotes: 0

Daniel
Daniel

Reputation: 11

The question is asking for a single global list of all items, so this will handle exactly that scenario.

To store the items:

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

In order to get access to the properties of a derived type, you will have to cast the Item to it's derived type.

To determine it's current type, use:

var itemType = item.GetType();

To use an item as it's derived type:

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

To get all items from the list which are a certain type:

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

Upvotes: 1

Related Questions