Reputation: 19
What I'm trying to do is for my game I'm developing I've got a messaging system that uses a telegram struct as follows:
public struct Telegram
{
private int sender;
private int receiver;
public int Receiver
{
get
{ return receiver; }
}
//Message of an enumerated messageToSend in Messages
private Message messageToSend;
public Message MessageToSend
{
get
{ return messageToSend; }
}
//for delayed messages
private double dispatchTime;
public double DispatchTime
{
get
{ return dispatchTime; }
set
{ dispatchTime = value; }
}
//for any additional info
private object extraInfo;
public object ExtraInfo
{
get
{ return extraInfo; }
}
public Telegram(double time, int otherSender, int otherReceiver,
Message otherMessage, object info = null)
{
dispatchTime = time;
sender = otherSender;
receiver = otherReceiver;
messageToSend = otherMessage;
extraInfo = info;
}
}
What I want to be able to do is since the extra info is something that is passed based on the message type and needs to be object, for convenience of not having to code a bunch of functions with various extra info types, I want to get the type of the object that its boxed to when the function gets passed the extra info variable.
I know that I can do that with .getType()
and store it in a Type
variable.
Here comes the tricky part that I'm not sure if I can do. What I want to do is then use that Type
variable to cast the object when the thing that received the telegram handles it based on the message type sent. Is that possible to do?
Can't use generic for the telegram class as it causes things to break when I just tried to convert my messaging code. Here is the rest of the relevant code:
/*Telegrams are stored in a priority queue. Therefore the < and ==
operators are overloaded so the PQ can sort the telegrams
by time priority. Times must be smaller than
SmallestDelay before two Telegrams are considered unique.*/
public const double SmallestDelay = 0.25;
public static bool operator ==(Telegram t1, Telegram t2)
{
return (Math.Abs(t1.dispatchTime - t2.dispatchTime) < SmallestDelay) &&
(t1.sender == t2.sender) &&
(t1.receiver == t2.receiver) &&
(t1.messageToSend == t2.messageToSend);
}
public static bool operator !=(Telegram t1, Telegram t2)
{
return (Math.Abs(t1.dispatchTime - t2.dispatchTime) > SmallestDelay) &&
(t1.sender != t2.sender) &&
(t1.receiver != t2.receiver) &&
(t1.messageToSend != t2.messageToSend);
}
public static bool operator <(Telegram t1, Telegram t2)
{
if (t1 == t2)
return false;
else
return (t1.dispatchTime < t2.dispatchTime);
}
public static bool operator >(Telegram t1, Telegram t2)
{
if (t1 == t2)
return false;
else
return (t1.dispatchTime > t2.dispatchTime);
}
sealed class MessageDispatcher
{
public const double sendMessageImmediately = 0.0;
public const int noAdditionalInfo = 0;
public const int senderIdIrrelevant = -1;
//a set is used as the container for the delayed messages
//because of the benefit of automatic sorting and avoidance
//of duplicates. Messages are sorted by their dispatch time.
private static SortedSet<Telegram> priorityQueue = new SortedSet<Telegram>();
/// <summary>
/// this method is utilized by DispatchMessage or DispatchDelayedMessages.
/// This method calls the messageToSend handling member function of the receiving
/// entity, receiver, with the newly created telegram
/// </summary>
/// <param name="receiver"></param>
/// <param name="messageToSend"></param>
private static void Discharge(ref BaseEntityInfo receiver, ref Telegram message)
{
if (!receiver.HandleMessage(ref message))
{
//telegram could not be handled
}
}
private MessageDispatcher() { }
public static readonly MessageDispatcher instance = new MessageDispatcher();
/// <summary>
/// given a messageToSend, a receiver, a sender and any time delay, this function
/// routes the messageToSend to the correct entity (if no delay) or stores it
/// in the messageToSend queue to be dispatched at the correct time. Entities referenced
/// by iD.
/// </summary>
/// <param name="delay"></param>
/// <param name="sender"></param>
/// <param name="otherReceiver"></param>
/// <param name="messageToSend"></param>
/// <param name="additionalInfo"></param>
public static void DispatchMessage(double delay, int sender,
int otherReceiver, Message message,
object additionalInfo = null)
{
//get the reciever
BaseEntityInfo receiver = EntityMgr.entityManager.GetEntityFromID(otherReceiver);
//make sure the Receiver is valid
if (receiver == null)
return;
//create the telegram
Telegram telegram = new Telegram(0, sender, otherReceiver, message, additionalInfo);
//if there is no delay, route telegram immediately
if (delay <= 0.0)
//send the telegram to the recipient
Discharge(ref receiver, ref telegram);
//else calculate the time when the telegram should be dispatched
else
{
double CurrentTime = Clock.Current();
telegram.DispatchTime = CurrentTime + delay;
//and put it in the queue
priorityQueue.Add(telegram);
}
}
/// <summary>
/// This function dispatches any telegrams with a timestamp that has
/// expired. Any dispatched telegrams are removed from the queue as it
/// sends out any delayed messages. This method is called each time through
/// the main game loop.
/// </summary>
public static void DispatchDelayedMessages()
{
double CurrentTime = Clock.Current();
//now peek at the queue to see if any telegrams need dispatching.
//remove all telegrams from the front of the queue that have gone
//past their sell by date
while (!(priorityQueue.Count == 0) &&
(priorityQueue.ElementAt(0).DispatchTime < CurrentTime) &&
(priorityQueue.ElementAt(0).DispatchTime > 0))
{
//read the telegram from the front of the queue
Telegram telegram = priorityQueue.ElementAt(0);
//find the recipient
BaseEntityInfo receiver = EntityMgr.entityManager.GetEntityFromID(telegram.Receiver);
//send the telegram to the recipient
Discharge(ref receiver, ref telegram);
//remove it from the queue
priorityQueue.Remove(priorityQueue.ElementAt(0));
}
}
}
Upvotes: 1
Views: 230
Reputation: 1063
You can use the Convert.ChangeType(object value, Type type)
see MSDN However, like the comments say, you are misusing a struct here as your object is not immutable, small, or logically representing one value, I would suggest considering a class
, and possibly a generic class
at that.
public class Telegram
{
public Telegram(double time, int otherSender, int otherReceiver,
Message otherMessage, object info = null)
{
DispatchTime = time;
Sender = otherSender;
Receiver = otherReceiver;
MessageToSend = otherMessage;
ExtraInfo = info;
}
public int Reciever {get; private set;}
public Message MessageToSend {get; private set;}
public double DispatchTime {get; set;}
public object ExtraInfo {get; private set;}
}
or as a generic:
public class Telegram<T>
{
public Telegram(double time, int otherSender, int otherReceiver,
Message otherMessage, T info = default(T))
{
DispatchTime = time;
Sender = otherSender;
Receiver = otherReceiver;
MessageToSend = otherMessage;
ExtraInfo = info;
}
public int Reciever {get; private set;}
public Message MessageToSend {get; private set;}
public double DispatchTime {get; set;}
public T ExtraInfo {get; private set;}
}
The advantage of a generic type parameter is that you can both easily know the type (typeof(T)
) and you can cast to the type without having to rely upon Convert.ChangeType
(T valueAsT = (T)value
) and you can impose constraints upon T
like where T: class, new()
says it has to be a reference type and it has to have a default constructor, or you can require an interface implementation, giving you access to it's members.
To Elaborate, a generic merely exposes type information about the member, so instead of needing to cast an object
to int
it is just already an int
.
For example:
var myTelegram = new Telegram(1235.3423, 42, 69, myMessageObj, 32);
var extraInfo = myTelegram.ExtraInfo;
var extraInfoType = extraInfo.GetType();
//extraInfoType is int, and you don't have to unbox it.
For more details on Generics, a great place to start is http://www.dotnetperls.com/generic - then you can dig in to MSDN or other resources.
Update:
As for what you just added to clarify, you can still use generics, but you will either have to put some type restrictions on it, or your SortedSet<T>
will have to be Telegram<object>
which does limit some things. But whatever you end up doing, useful information is that you can use generics at the method level as well, so you don't have to always declare an class as generic, for example:
public sealed class MessageDispatcher
{
public static void DispatchMessage<T>(double delay, int sender,
int otherReceiver, Message message,
T additionalInfo = default(T))
...
}
I did just to be sure, copy all the code you posted and made a version with generics, so It can be done, but with generics you also have to override the GetHashCode()
method and the Equals(obj other)
method (inherited from System.Object
) in order to get it to compile because you overloaded the operators.
However, if you can't restrict the type to say something like IComparable<T>
or some other interface where you would benefit from knowing some properties about the type, then you might as well go with object, and then use the value as Type
casting or the Convert.ChangeType(Object obj, Type type)
.
public class Telegram<T>
{
public int Sender { get; private set; }
public int Receiver { get; private set; }
//Message of an enumerated messageToSend in Messages
public Message MessageToSend { get; private set; }
//for delayed messages
public double DispatchTime { get; set; }
//for any additional info
public T ExtraInfo { get; private set; }
public Telegram(double time, int otherSender, int otherReceiver,
Message otherMessage, T info = default(T))
{
DispatchTime = time;
Sender = otherSender;
Receiver = otherReceiver;
MessageToSend = otherMessage;
ExtraInfo = info;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
/*Telegrams are stored in a priority queue. Therefore the < and ==
operators are overloaded so the PQ can sort the telegrams
by time priority. Times must be smaller than
SmallestDelay before two Telegrams are considered unique.*/
public const double SmallestDelay = 0.25;
public static bool operator ==(Telegram<T> t1, Telegram<T> t2)
{
return (Math.Abs(t1.DispatchTime - t2.DispatchTime) < SmallestDelay) &&
(t1.Sender == t2.Sender) &&
(t1.Receiver == t2.Receiver) &&
(t1.MessageToSend == t2.MessageToSend);
}
public static bool operator !=(Telegram<T> t1, Telegram<T> t2)
{
return (Math.Abs(t1.DispatchTime - t2.DispatchTime) > SmallestDelay) &&
(t1.Sender != t2.Sender) &&
(t1.Receiver != t2.Receiver) &&
(t1.MessageToSend != t2.MessageToSend);
}
public static bool operator <(Telegram<T> t1, Telegram<T> t2)
{
if (t1 == t2)
return false;
else
return (t1.DispatchTime < t2.DispatchTime);
}
public static bool operator >(Telegram<T> t1, Telegram<T> t2)
{
if (t1 == t2)
return false;
else
return (t1.DispatchTime > t2.DispatchTime);
}
}
public sealed class MessageDispatcher
{
public const double sendMessageImmediately = 0.0;
public const int noAdditionalInfo = 0;
public const int senderIdIrrelevant = -1;
//a set is used as the container for the delayed messages
//because of the benefit of automatic sorting and avoidance
//of duplicates. Messages are sorted by their dispatch time.
private static SortedSet<Telegram<object>> priorityQueue = new SortedSet<Telegram<object>>();
/// <summary>
/// this method is utilized by DispatchMessage or DispatchDelayedMessages.
/// This method calls the messageToSend handling member function of the receiving
/// entity, receiver, with the newly created telegram
/// </summary>
/// <param name="receiver"></param>
/// <param name="messageToSend"></param>
private static void Discharge<T>(BaseEntityInfo receiver, Telegram<T> message)
{
if (!receiver.HandleMessage(message))
{
//telegram could not be handled
}
}
private MessageDispatcher() { }
public static readonly MessageDispatcher instance = new MessageDispatcher();
/// <summary>
/// given a messageToSend, a receiver, a sender and any time delay, this function
/// routes the messageToSend to the correct entity (if no delay) or stores it
/// in the messageToSend queue to be dispatched at the correct time. Entities referenced
/// by iD.
/// </summary>
public static void DispatchMessage<T>(double delay, int sender,
int otherReceiver, Message message,
T additionalInfo = default(T))
{
//get the reciever
BaseEntityInfo receiver = EntityMgr.entityManager.GetEntityFromID(otherReceiver);
//make sure the Receiver is valid
if (receiver == null)
return;
//create the telegram
var telegram = new Telegram<object>(0, sender, otherReceiver, message, additionalInfo);
//if there is no delay, route telegram immediately
if (delay <= 0.0)
//send the telegram to the recipient
Discharge(receiver, telegram);
//else calculate the time when the telegram should be dispatched
else
{
double CurrentTime = Clock.Current();
telegram.DispatchTime = CurrentTime + delay;
//and put it in the queue
priorityQueue.Add(telegram);
}
}
/// <summary>
/// This function dispatches any telegrams with a timestamp that has
/// expired. Any dispatched telegrams are removed from the queue as it
/// sends out any delayed messages. This method is called each time through
/// the main game loop.
/// </summary>
public static void DispatchDelayedMessages()
{
double CurrentTime = Clock.Current();
//now peek at the queue to see if any telegrams need dispatching.
//remove all telegrams from the front of the queue that have gone
//past their sell by date
while (!(priorityQueue.Count == 0) &&
(priorityQueue.ElementAt(0).DispatchTime < CurrentTime) &&
(priorityQueue.ElementAt(0).DispatchTime > 0))
{
//read the telegram from the front of the queue
var telegram = priorityQueue.ElementAt(0);
//find the recipient
BaseEntityInfo receiver = EntityMgr.entityManager.GetEntityFromID(telegram.Receiver);
//send the telegram to the recipient
Discharge(receiver, telegram);
//remove it from the queue
priorityQueue.Remove(priorityQueue.ElementAt(0));
}
}
}
Upvotes: 0
Reputation: 2368
I would suggest using the Convert.ChangeType
option. Using generics in this case wouldn't add much benefit and could back you into a corner within the MessageDispatcher
class because when you create SortedSet<Telegram>
you would have to specify SortedSet<Telegram<T>>
, but T could be anything and if I remember right (correct me if I'm wrong) having a collection of different Ts won't work.
Each handler needs to know the "type" of the extra info and how to handle the data, so passing around an object or a base class would be enough. The dispatcher doesn't care what type the extra info is or whether the receiver can actually handle the extra info. When the receiver handles a Telegram it can simply call GetType
on the extra info, pass that into the ChangeType
method and if it converts successfully, then great...if not then handle the cast error.
EDIT 1: You can simple do a MyRealType result = extraInfo as MyRealType
, then if result
is NULL then you know the wrong type of extra info has been past to the receiver.
Upvotes: 0
Reputation: 28345
First of all, why use struct
in this case? Structs in C# isn't the same that they are in C++ - in this case your object will be copied each time it's being passed in method, and this can be a very memory consuming solution.
Second, try to use the Generic
method, like this:
public Telegram<T>(double time, int otherSender, int otherReceiver,
Message otherMessage, T info = null)
where T : class
{
dispatchTime = time;
sender = otherSender;
receiver = otherReceiver;
messageToSend = otherMessage;
// check for T here
}
If you'd clarify what exactly you want to achieve, the community can help you better way.
Usage of generic method is like a template method in a C++. The only problem is if you want a Parametrized constructor, you must declare a generic class, like this:
public class Telegram<T>
{
T additionalInfo;
Telegram(double time, int otherSender, int otherReceiver,
Message otherMessage, T info = null)
where T : class
Upvotes: 1