Placing different generic types into a data structure, and then handling them upon removal

元气小坏坏 提交于 2020-01-25 06:59:42

问题


I posted this question yesterday but it was marked as a duplicate of C# - Multiple generic types in one list

To clarify, the answer in that problem shows how to correctly place multiple generic types into a single list. However that's not my problem. My problem is that, upon removing items from the list, I need to know what type they are in order to handle them correctly.

As a starting point, I am using the answer to the above question because it does have a good solution for placing items of multiple types into a data structure. As you can see below, I have created an abstract base ScheduleItem class, and then an inheriting generic ScheduleItem class. That allows me pass to in different types of generic to the same priority queue.

However when I dequeue these items, they are treated as the base class, which do not have the properties of the inheriting class that I am trying to access. Therefore trying to get any data out of them doesn't work. Even though, in the debugger, I can clearly see that the object I'm pulling out contains the data I want, I get errors trying to access it. I assume this is because when I'm dequeuing an object I don't know what generic type was associated with it, and so I get the base class, which has no properties associated.


public class ScheduleManager
{
    PriorityQueue<ScheduleItem> schedule = new PriorityQueue<ScheduleItem>();

    //case: adding an item with int data
    public void AddScheduleItem(int steps, string eventName, int param) {
        schedule.Enqueue(new ScheduleItem<int>(eventName, param, steps), steps);
    }

    //case: adding an item with bool data
    public void AddScheduleItem(int steps, string eventName, bool param)
        schedule.Enqueue(new ScheduleItem<bool>(eventName, param, steps), steps);
    }

    public ScheduleManager()
    {
        schedule.Enqueue(new ScheduleItem<bool>("test", true, 1), 1);
        ScheduleItem test = schedule.Dequeue();

        Debug.Log(test.ScheduledStep); //Coming up null
        Debug.Log(test.Data); //Coming up null
        test.Activate(); //Causing error
    }

    //The abstract base class
    abstract class ScheduleItem { }

    //The inheriting generic class. This is the one that actually 
    //holds data I need
    class ScheduleItem<T> : ScheduleItem where T : struct {
        public T Data { get; private set; }
        public string EventName { get; private set; }
        public int ScheduledStep { get; private set; }

        public ScheduleItem(string eventName, T data, int scheduledStep) {
            EventName = eventName;
            Data = data;
            ScheduledStep = scheduledStep;
        }

        public void Activate() {
            if(typeof(T) == typeof(int)) {
                int data = (int)Convert.ChangeType(Data, typeof(int));
                EventManager.TriggerIntEvent(EventName, data);
            }

            if (typeof(T) == typeof(bool))
            {
                bool data = (bool)Convert.ChangeType(Data, typeof(bool));
                EventManager.TriggerBoolEvent(EventName, data);
            }
        }
    }
}

Any help would be greatly appreciated!


回答1:


Back to OOP basics.

You want EventName and ScheduledStep to be available regardless of the type of ScheduleItem. Therefore they need to be on the ScheduleItem class itself.

You also want the behaviour of each ScheduleItem subclass to be different, depending on the type of its data. An easy way to do this is to have a different subclass per type of data, which has a different Activate method.

public abstract class ScheduleItem
{
    public string EventName { get; }
    public int ScheduledStep { get; }

    protected ScheculeItem(string eventName, int scheduledStep)
    {
        EventName = eventName;
        ScheduledStep = scheduledStep;
    }
    public abstract void Activate();
}

public class IntScheduleItem
{
    private readonly int data;
    public IntScheduleItem(string eventName, int scheduledStep, int data)
        : base(eventName, int scheduledStep)
    {
        this.data = data;
    }

    public override void Activate()
    {
         EventManager.TriggerIntEvent(EventName, data);
    }
}

... and so on

If the only difference between the different types of ScheduleItem is the EventManager method which is called, you could do something like this instead of having separate IntScheduleEvent, BoolScheduleEvent, etc, classes:

public class ScheduleItem<T> : ScheduleItem
{
    private readonly T data;
    private readonly Action<string, T> activator;

    public ScheduleItem(string eventName, int scheduledStep, int data, Action<string, T> activator)
        : base(eventName, int scheduledStep)
    {
        this.data = data;
        this.activator = activator;
    }

    public override void Activate()
    {
        activator(EventName, data);
    }
}

...

public void AddScheduleItem(int steps, string eventName, int param) {
    schedule.Enqueue(new ScheduleItem<int>(eventName, steps, param, EventManager.TriggerIntEvent), steps);
}

You could even take it one step further and do away with the class hierarchy at all:

public class ScheduleItem
{
    public string EventName { get; }
    public int ScheduledStep { get; }
    private readonly Action activator;

    protected ScheculeItem(string eventName, int scheduledStep, Action activator)
    {
        EventName = eventName;
        ScheduledStep = scheduledStep;
        this.activator = activator;
    }
    public void Activate() => activator();
}

...

public void AddScheduleItem(int steps, string eventName, int param) {
    schedule.Enqueue(new ScheduleItem(eventName, steps, () => EventManager.TriggerIntEvent(eventName, param)), steps);


来源:https://stackoverflow.com/questions/59826123/placing-different-generic-types-into-a-data-structure-and-then-handling-them-up

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!