1

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));
        }
    }
}
  • 5
    Can you explain why you think a `struct` is a good idea here? – spender Feb 08 '15 at 18:11
  • 3
    ["Choosing Between Class and Struct"](https://msdn.microsoft.com/en-us/library/ms229017%28v=vs.110%29.aspx) says the following: AVOID defining a struct unless the type has all of the following characteristics: (a) It logically represents a single value, similar to primitive types (int, double, etc.). (b) It has an instance size under 16 bytes. (c) It is immutable. (d) It will not have to be boxed frequently. *By my reckoning, you're failing on all four counts.* – spender Feb 08 '15 at 18:15
  • Because I don't need telegram as a class. – Thomas Morse Feb 08 '15 at 18:16
  • 2
    Why do you think that? You're wrong to use a struct here. Why do you think it gives you an advantage over a class? – spender Feb 08 '15 at 18:16
  • possible duplicate of [Casting a variable using a Type variable](http://stackoverflow.com/questions/972636/casting-a-variable-using-a-type-variable) – Preston Guillot Feb 08 '15 at 18:18
  • Is the underlying type of `ExtraInfo` known only at runtime? Generics might be easier in the long run, depending on the rest of your app. – Tim Feb 08 '15 at 18:19
  • As for what you had just posted, it logically makes sense for as a single value to me, it has an instance size of under 16 bytes for the most part, no clue on immutable, and boxing for the struct isn't done frequently only a type within it. – Thomas Morse Feb 08 '15 at 18:21
  • @ThomasMorse This won't have an instance size of under 16 bytes. A double is 8 bytes and an int is 4. Then you have your unknown object type, which could be anything at this point. – Steve Feb 08 '15 at 18:27

3 Answers3

1

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
VMAtm
  • 27,943
  • 17
  • 79
  • 125
  • Didn't know that with structs as I was taught in C++ and been trying to self teach myself C#. Also wouldn't having a generic method require me to create a method each time I want to send a different type of info? – Thomas Morse Feb 08 '15 at 18:35
  • @ThomasMorse And no, no need to other method - may be only a new logic for additional info for a particular type. – VMAtm Feb 08 '15 at 19:16
0

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));
        }
    }
}
tophallen
  • 1,033
  • 7
  • 12
  • The issue with using a generic type is that then I'd have a whole bunch of copies of telegram sitting around taking up memory when I only need one type of telegram, as well as having to implement a telegram class for each type of extra info I'd want to send which could range into the about one hundred. – Thomas Morse Feb 08 '15 at 18:33
  • you wouldn't have to implement more than one instance, generics are true generics in C#, unlike Java, a class of `Telegram` can be used everywhere regardless of the type `T` is. Even at runtime. – tophallen Feb 08 '15 at 18:34
  • OK I didn't know that. So then how would I call that function? Not to familiar with generics. – Thomas Morse Feb 08 '15 at 18:38
  • If the compiler can figure out what you are passing it, then it handles the hard work for you, if you were say, passing in a `dynamic` type, then you might want to constrict the type by explicity declaring it like so: `new Telegram(23, 42, 69, myMessage, myDynamicExtraInfo);` – tophallen Feb 08 '15 at 18:40
  • Also a side note that I noticed, you put the variables as public but then changed the set to private. Does that work equivalently to what I'm doing without exposing the variables more than what I'd want? – Thomas Morse Feb 08 '15 at 18:43
  • Yes, in a class you you can take advantage of auto-getters and setters, this means that `public int Prop {get;private set;}` is compiled like you had it, it is creating the backing field that it references and setting it to private, and only exposing the get method as public. – tophallen Feb 08 '15 at 18:44
  • C# has extensive syntactic sugar, I suggest checking out some of those features as you are learning, wikipedia has some good coverage of some of those features and how they differ from C/C++ http://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29http://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29 – tophallen Feb 08 '15 at 18:46
  • Just found out that I can't use a generic telegram class as it breaks the rest of my messaging system code and I'm not quite sure how to change it so it will work with generics. :S – Thomas Morse Feb 08 '15 at 19:05
  • You said that I may have some limitation issues with doing SortedSet will have to be Telegram. I'm wondering if it will cause issues with me wanting to be able to pass anything from a int, string, enum, class as I don't think there will be anything other than stuff like that which I will pass in. – Thomas Morse Feb 08 '15 at 19:44
  • Anything can be boxed into an object, but you would have to explicitly box it in order to put it into the set, Like I said, if you can't restrict the type to something that benefits you knowing beyond it being an object, then generics might not be a good fit, I would just use it as you had it before - but as a class, and use the `value as Type` to cast it out later, or use `Convert.ChangeType(object obj, Type type)` – tophallen Feb 08 '15 at 19:46
  • OK. Not sure if I'm just having issues or not understanding how to set this up, but I'm trying to use convert.changeType to cast the object so I can do something like health -= extrainfo (as they would both be int) but can't. Its still looking for (int) before the call to convert. :S – Thomas Morse Feb 08 '15 at 20:29
  • The syntax would end up looking something like this `int a = (int)Convert.ChangeType(myTelegram.ExtraInfo, typeof(int));` or `int a Convert.ChangeType(myTelegram.ExtraInfo, typeof(int)) as int;` it returns it as a boxed instance of the type. – tophallen Feb 08 '15 at 21:54
0

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.

MotoSV
  • 2,348
  • 17
  • 27