Search code examples
c#classunity-game-engineabstract-class

Auto casting abstracted classes in c#


I'm trying to get my head around making a simplified way of making items, lists and databases in C# And while everything works, It involved me needing to cast the results out.

So far I have the following

namespace Game.Database
{

    public abstract class DatabaseItem : ScriptableObject
    {

    }

    public class DatabaseList : ScriptableObject
    {
        public List<DatabaseItem> items;
    }

    public class ShipClass : ScriptableObject
    {
        public string shipClassID;
        new public string name;
    }

    public class Ship : DatabaseItem
    {
        public string shipID;
        new public string name;
        public ScriptableObject shipClass;
    }

}

public class Database : MonoBehaviour
{
    public List<DatabaseList> lists;

     void Start()
    {
        Ship ship = (Ship)lists[0].items[0];
        Debug.Log(shipClass.shipID);
        ShipClass shipClass = (ShipClass)ship.shipClass;
        Debug.Log(shipClass.shipClassID);
    }
}

Bear in mind this is a unity project so these items are being instantiated and data being assigned through the UI.

As you can see I have an abstract for my items and will have multiple types of item, and multiple lists. I am trying to avoid having to make multiple class' for my lists, one for each type of item. So i have abstracted my items off DatabaseItem so that I can store a List of DatabaseItem in my DatabaseList. However this means when reading my data out i need to cast this back into a Ship class.

While this isn't bad for a simple implementation, in production these will be nested requiring multiple casts to get down to the required data.

Unfortunately I find myself lacking in the required c# vocabulary to really google the issue. Looking at the Microsoft User-defined conversion operators and their example just doesn't make sense if it's even what i want to achieve.

EDIT - The issue if not accessing the data, as I can do that, it's having to break down every level of the data as in the end this will be very generic used for all game data and very nested, so having to cast every level out to be able to break it down is what I'm trying to avoid.


Solution

  • One way would be to expose a method on each type of item that does the writing out of the data, so the calling code doesn't need to know the low level details.

    See below, on how to avoid doing any casting.

    namespace Game.Database
    {
    
        public abstract class DatabaseItem : ScriptableObject
        {
             public abstract void WriteOut();
        }
    
        public class DatabaseList : ScriptableObject
        {
            public List<DatabaseItem> items;
        }
    
        public class Ship : DatabaseItem
        {
            public string shipID;
            new public string name;
    
            public override void WriteOut()
            {
                Debug.Log(shipID);
            }
        }
    
    }
    
    public class Database : MonoBehaviour
    {
        public List<DatabaseList> lists;
    
         void Start()
         {
            lists[0].items[0].WriteOut();
         }
    }
    

    This way you are allowing each item type to handle its own writing out. Id suggest thinking carefully about your API.

    To be even more SOLID and clean, you could use dependency injection and inject the writing into the type, see below for another example.

    This has the benefit of allowing multiple types to use the same writer code and you also keep your class following the single responsibility principle.

    namespace Game.Database
    {
        public interface IWriter
        {
            void Write(string output);
        }
    
        public class ConsoleWriter: IWriter
        {
            public void Write(string output)
            {
                 Debug.Log(output);
            }
        }
    
        public abstract class DatabaseItem : ScriptableObject
        {
             public abstract void WriteOut();
        }
    
        public class DatabaseList : ScriptableObject
        {
            public List<DatabaseItem> items;
        }
    
        public class Ship : DatabaseItem
        {
            private IWriter _writer;
    
            public Ship(IWriter writer)
            {
                _writer = writer;
            }
    
            public string shipID;
            new public string name;
    
            public override void WriteOut()
            {
                _writer.Write(shipID);
            }
        }
    }
    
    public class Database : MonoBehaviour
    {
        public List<DatabaseList> lists;
    
         void Start()
         {
            lists[0].items[0].WriteOut();
         }
    }