I am struggling with a specific dictionary for changing states of an object.
The idea is that I have class Process, which is created everytime user submit something to be process. Process has ID, some other info and CurrentState. Possible CurrentStates are defined in StateEnum class and contains states like "Pending, Processing, Completed, Error" etc. After that, there are some tasks/commands, that user can perform - each of this task is defined as a class as well and contains ID of process and Execute method, that changes the state of object. These tasks inherits from interface ITask.
Thing is, that I require some kind of state-machine, that would verify whether it is possible to move from state "Pending" to state "Processing" using task "ResumeTask" for example. For this verification, I have created class State with a dictionary and its object is created each time the Process is created.
Problem is, that the dictionary is not working properly. Let me show the code (I have created simple Console Application just to demonstrate the problem) with some comments and explain what is happening:
ITask interface
public interface ITask
{
int IntParam1 { get; set; }
void Execute(Process proc);
}
ResumeTask class (I have also PauseTask and other Tasks)
class ResumeTask : ITask
{
int intParam1;
public int IntParam1
{
get { return intParam1; }
set { intParam1 = value; }
}
public void Execute(Process proc)
{
ResumeTask resume = new ResumeTask();
proc.ChangeState(resume);
//code below not working - is there a way how to make this
//work instead of creating new empty class above?
//proc.ChangeState(this);
}
public override int GetHashCode()
{
return 17 + 31 * intParam1.GetHashCode();
}
public override bool Equals(object obj)
{
ResumeTask other = obj as ResumeTask;
return other != null && this.intParam1 == other.intParam1;
}
}
Process class
public class Process
{
State stat;
private int processID;
public int ProcessID
{
get { return processID; }
private set { processID = value; }
}
private StateEnum state;
public StateEnum State
{
get { return state; }
set { state = value; }
}
public readonly int userID;
public int UserID
{
get { return userID; }
}
public Process(int processID, int userID)
{
this.processID = processID;
this.userID = userID;
stat = new State();
this.state = stat.CurrentState;
}
public void ChangeState(ITask task)
{
this.state = stat.MoveNext(task);
}
}
State class
class State
{
private class StateTransition
{
public StateEnum State;
public ITask Task;
public StateTransition(StateEnum state, ITask task)
{
State = state;
Task = task;
}
public override int GetHashCode()
{
return 17 + 31 * State.GetHashCode() + 31 * Task.GetHashCode();
}
public override bool Equals(object obj)
{
StateTransition other = obj as StateTransition;
return other != null && this.State == other.State && this.Task == other.Task;
}
}
public StateEnum CurrentState;
Dictionary<StateTransition, StateEnum> transitions;
ITask pause = new PauseTask();
ITask resume = new ResumeTask();
ITask resumenew = new ResumeTask();
public State()
{
CurrentState = StateEnum.Pending;
transitions = new Dictionary<StateTransition, StateEnum>
{
{ new StateTransition(StateEnum.Processing, pause), StateEnum.Pending },
{ new StateTransition(StateEnum.Pending, resume), StateEnum.Processing }
};
}
public StateEnum MoveNext(ITask task)
{
StateTransition transition = new StateTransition(CurrentState, task);
StateEnum nextState;
if (!transitions.TryGetValue(transition, out nextState))
{
return StateEnum.Error;
}
else
{
return nextState;
}
}
}
StateEnum enum
public enum StateEnum
{
Pending,
Processing,
Error,
Processed,
Downloaded
}
Program class
class Program
{
static void Main(string[] args)
{
Process proc = new Process(1, 20);
ResumeTask resumetask = new ResumeTask();
resumetask.IntParam1 = 1;
resumetask.Execute(proc);
Console.WriteLine(Convert.ToString(proc.ProcessState));
Console.ReadLine();
}
}
Lets quickly go through some important points:
- In all tasks, I have overridenn Equals and GetHashCode methods, because Ive learned that is has to be done like it, if I want to use object in dictionary.
- If you notice the execute method - problem is, that when I was testing it before on a simplier example, I had to pass new object without any values, in order to get some succesfuly results - maybe thats because in dictionary, I have empty class but I was passing class with intParam1 set and try to compare them?
- In Process class, I simply create new State object and acquire CurrentState from it. Also, the method MoveNext simply pass along the input parameter from Execute() method in Task
- In State class, there is the biggest issue (I think). As you can see, I had to create another type "StateTransition" (this is borrowed from state-machine example here on StackOverflow) because in real application, there are more Tasks that could be performed on certain state - and in order to have unique keys in dictionary, I had to do it like that. So basicaly I create the type, again override the Equald and GetHashCode and after that, I create dictionary, in which StateTransition serves as a key. I had to create empty objects of Tasks (pause, resume) in order to put them in dictionary - I am not sure whether this is the right approach, but it was working to some point. After that, I try to find a value for the key, which consists of a CurrentState in Process and task, that I passed in Execute() method before. But the result is always "Error".
I have tried several things - I tried to create 2 simple dictionaries:
transitions
<ITask, StateEnum>
;
availableTasks<StateEnum,ITask>
;and it was working fine - I checked them in MoveNext method and was able to make valid move - the problem was, that with this approach, I couldn create the scenario, in which I need to have multiple tasks available for 1 state.
- I have also tried to experiment with the declaration of
StateTransition transition = new StateTransition(CurrentState, task);
In MoveNext method in State class - for example if I put object "resume" (which was created previously in order to add it in dictionary) instead of "task" parameter, it is working - because I am basicaly comparing 2 same objects. But, as you can see, I have also tried to define object
ITask resumenew = new ResumeTask();
and pass it again instead of "task" parameter - it was not working.
To sum it up - is this generaly bad approach or do I have some small mistake somewhere, that I am not aware of? I hope this question is not too chaotic - in case on any question, I can edit it or answer in comments. Thank you for any help.
EDIT: If I leave the rest as it is and just change State class like this:
class State
{
public StateEnum CurrentState;
Dictionary<ITask, StateEnum> transitions;
ITask pause = new PauseTask();
ITask resume = new ResumeTask();
public State()
{
CurrentState = StateEnum.Pending;
transitions = new Dictionary<ITask, StateEnum>
{
{pause, StateEnum.Pending },
{resume, StateEnum.Processing }
};
}
public StateEnum MoveNext(ITask task)
{
StateEnum nextState;
if (!transitions.TryGetValue(task, out nextState))
{
return StateEnum.Error;
}
else
{
return nextState;
}
}
}
It is working - if state is StateEnum.Pending and I pass task ResumeTask resumetask = new ResumeTask() in MoveNext() method, it will return me StateEnum.Processing, which is correct.
So the "simple" object key dictionary is working, but when I encapsulate that object into another object, then despite of overriding Equals and GetHashCode again, I cant get a match (that implementation is shown in main post - State class).
Question is, why I cant get a match, if I am basicaly passing object of the same class.
EDIT2:
This was too complicated for the purpose I needed. I have tried different approach using List<KeyValuePair<Enum,Object>>
since I dont need to change the dictionary after creating it and list has no problems with same key for more values. So far, it is working.
I can post the final code if needed.