[.net] Dynamic type-casting

Started by
11 comments, last by qingrui 15 years, 9 months ago
Hi all, I have been checking out SDL.net and was intrigued by one of their examples. It is basically an event system where objects send events to a event manager and the event manager fires them to the observer to make things happen. The piece of code to handle this goes like this:

 public void Publish(Object obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }
            if (obj.GetType().Name == "GameStatusEventArgs")
            {
                if (OnGameStatusEvent != null)
                {
                    //LogFile.WriteLine("EventManager has received GameStatus event");
                    OnGameStatusEvent(this, (GameStatusEventArgs)obj);
                }
            }
            else if (obj.GetType().Name == "EntityMoveRequestEventArgs")
            {
                if (OnEntityMoveRequestEvent != null)
                {
                    //LogFile.WriteLine("EventManager has received EntityMoveRequest event");
                    OnEntityMoveRequestEvent(this, (EntityMoveRequestEventArgs)obj);
                }
            }
            else if (obj.GetType().Name == "MapBuiltEventArgs")
            {
                if (OnMapBuiltEvent != null)
                {
                    //LogFile.WriteLine("EventManager has received a MapBuilt event");
                    OnMapBuiltEvent(this, (MapBuiltEventArgs)obj);
                }
            }
            else if (obj.GetType().Name == "EntityMoveEventArgs")
            {
                if (OnEntityMoveEvent != null)
                {
                    //LogFile.WriteLine("EventManager has received an EntityMove event");
                    OnEntityMoveEvent(this, (EntityMoveEventArgs)obj);
                }
            }
            else if (obj.GetType().Name == "EntityPlaceEventArgs")
            {
                if (OnEntityPlaceEvent != null)
                {
                    //LogFile.WriteLine("EventManager has received an EntityPlace event");
                    OnEntityPlaceEvent(this, (EntityPlaceEventArgs)obj);
                }
            }
        }
I was thinking -- I could use a dictionary to store the name of the type, and store the delegate for it to call -- or some other way to get rid of the if-elseif block (which becomes unmanagable if the game grows larger). But I am stumped at how should I cast the the event arguments before calling the delegate. How could I make this code more generic and automated without having to test for each type of event? Original source: http://cs-sdl.svn.sourceforge.net/viewvc/cs-sdl/trunk/SdlDotNet/examples/SdlDotNetExamples/SimpleGame/EventManager.cs?revision=1292&view=markup
GamesTopica.Net- http://www.gamestopica.net
Advertisement
Converting the type name to a string and then performing a string comparison is one way of doing it, I guess, though I'm not sure why they don't just use the is keyword.

As for the main question, I'm not sure why you'd want to use such a manager, but I can't personally think of a sensible answer that doesn't use reflection.

[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]

This is where you need Dynamic Dispatch.

edit: actually, that's a really poorly written article.

[Edited by - capn_midnight on June 19, 2008 5:28:44 PM]

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

Quote:Original post by benryves
Converting the type name to a string and then performing a string comparison is one way of doing it, I guess, though I'm not sure why they don't just use the is keyword.

As for the main question, I'm not sure why you'd want to use such a manager, but I can't personally think of a sensible answer that doesn't use reflection.


I am still new to reflection. Are there any samples or code snippets laying around to show how it could be done? All I could get from google is Emit and GetType() from the MSDN, and no examples on a real life usage like this.
GamesTopica.Net- http://www.gamestopica.net
Quote:Original post by Extrakun
Are there any samples or code snippets laying around to show how it could be done?
Here's a fairly ghastly example. [smile]

It assumes that all the specific event methods to be invoked are within the same class as Publish. To identify the methods that need to be invoked, it checks for a ManagedEvent attribute. The event methods to invoke must take two parameters, and the second parameter is used to map a type to a particular handler.

class ReflectionDemo {	#region Event-type specific methods	// This attribute is used to mark the event methods you wish to control via the manager.	[AttributeUsage(AttributeTargets.Method)]	private class ManagedEventAttribute : Attribute { }	[ManagedEvent()]	public void OnSomeKeyboardEvent(object sender, KeyEventArgs e) {		Console.WriteLine("OnSomeKeyboardEvent");	}	[ManagedEvent()]	public void OnSomeMouseEvent(object sender, MouseEventArgs e) {		Console.WriteLine("OnSomeMouseEvent");	}	#endregion	// This maps types to the event method to invoke.	private Dictionary<Type, MethodInfo> TypeToMethodMap;	// This constructor sets everything up.	public ReflectionDemo() {		this.TypeToMethodMap = new Dictionary<Type, MethodInfo>();				// Iterate over all methods in this particular class.		foreach (var Method in this.GetType().GetMethods()) {			// Grab the method's parameters.			var Parameters = Method.GetParameters();			// Check if the method is marked with the ManagedEventAttribute and if it takes two parameters.			if (Method.GetCustomAttributes(typeof(ManagedEventAttribute), false).Length > 0 && Parameters.Length == 2) {				this.TypeToMethodMap.Add(Parameters[1].ParameterType, Method);			}		}	}	public void Publish(object o) {		var MethodToInvoke = this.TypeToMethodMap[o.GetType()];		MethodToInvoke.Invoke(this, new object[] { this, o });	}}


I've only really tried it with the following:
static void Main(string[] args) {	var R = new ReflectionDemo();	R.Publish(new KeyEventArgs(Keys.X));	R.Publish(new MouseEventArgs(MouseButtons.Middle, 1, 0, 0, 0));}

I'm still not sure how this would ever be useful, though. [grin]

[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]

Hey thanks, this is helpful for getting me started, but it seems that Reflection requires quite a number of meta-tag. Going to the library to grab a C# book will help, I guess :)

I am now solving the problem the old fashioned way using casting; but how much is the performance impact of reflection for games, considering that events are likely to be sent at 25 FPS?
GamesTopica.Net- http://www.gamestopica.net
From what I can see, all this achieves is having a method with a single name -- Publish() -- that calls other methods depending on the type of data it was passed. Why couldn't you do:
public void Publish(GameStatusEventArgs e) {  if (OnGameStatusEvent != null) OnGameStatusEvent(this, e);}public void Publish(EntityMoveRequestEventArgs e) {  if (OnEntityMoveRequestEvent != null) OnEntityMoveRequestEvent(this, e);}public void Publish(MapBuiltEventArgs e) {  if (OnMapBuiltEvent != null) OnMapBuiltEvent(this, e);}
This solves your type-casting concerns and also provides you with compile-time type checking, and doesn't require any reflection.

[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]

Being as I've not posted any poop code in a while I though it's about time I did ;) here's another alternative using reflection.

		public class Event1Args { public int val; }		public class Event2Args { public int val; }		public class Event3Args { public int val; }		public class SomeClass		{			private Dictionary<Type, string> _eventTypes = new Dictionary<Type, string>();			public SomeClass()			{				_eventTypes.Add(typeof(Event1Args),"OnEvent1");				_eventTypes.Add(typeof(Event2Args),"OnEvent2");				_eventTypes.Add(typeof(Event3Args),"OnEvent3");			}			public void OnEvent1(SomeClass p, Event1Args e) { Debug.WriteLine("OnEvent1"); }			public void OnEvent2(SomeClass p, Event2Args e) { Debug.WriteLine("OnEvent2"); }			public void OnEvent3(SomeClass p, Event3Args e) { Debug.WriteLine("OnEvent3"); }			public void Publish(object obj)			{				string function = _eventTypes[obj.GetType()];				if (!string.IsNullOrEmpty(function))				{					MethodInfo efunc = GetType().GetMethod(function);					if (efunc != null) efunc.Invoke(this, new object[] { this, obj });				}			}		}...			Event1Args a1 = new Event1Args();			Event2Args a2 = new Event2Args();			Event3Args a3 = new Event3Args();			SomeClass sc = new SomeClass();			sc.Publish(a1);			sc.Publish(a2);			sc.Publish(a3);


EDIT: I just actually looked at benryves example, mine is just a nooby version of the same thing, my apologies.
You don't really need reflection for this. What you want is the Visitor pattern, which is what I was looking for before. It's a means of implementing dynamic, multi-dispatch in a language that has only single-dispatch.

So here is a stub of your original sample
using System;namespace DynamicDispatchSample{    class Program    {        static void A(EventPublisher p, GameStatusEventArgs e)        {            Console.WriteLine("A");        }        static void B(EventPublisher p, EntityMoveRequestEventArgs e)        {            Console.WriteLine("B");        }        static void C(EventPublisher p, MapBuiltEventArgs e)        {            Console.WriteLine("C");        }        static void D(EventPublisher p, EntityMoveEventArgs e)        {            Console.WriteLine("D");        }        static void E(EventPublisher p, EntityPlaceEventArgs e)        {            Console.WriteLine("E");        }        static void Main(string[] args)        {            EventPublisher p = new EventPublisher();            p.OnGameStatusEvent += new GameStatusEvent(A);            p.OnEntityMoveRequestEvent += new EntityMoveRequestEvent(B);            p.OnMapBuiltEvent += new MapBuiltEvent(C);            p.OnEntityMoveEvent += new EntityMoveEvent(D);            p.OnEntityPlaceEvent += new EntityPlaceEvent(E);            p.Publish(new GameStatusEventArgs());            p.Publish(new EntityMoveRequestEventArgs());            p.Publish(new MapBuiltEventArgs());            p.Publish(new EntityMoveEventArgs());            p.Publish(new EntityPlaceEventArgs());        }    }    class EventPublisher    {        public void Publish(Object obj)        {            if (obj == null)            {                throw new ArgumentNullException("obj");            }            if (obj.GetType().Name == "GameStatusEventArgs")            {                if (OnGameStatusEvent != null)                {                    //LogFile.WriteLine("EventManager has received GameStatus event");                    OnGameStatusEvent(this, (GameStatusEventArgs)obj);                }            }            else if (obj.GetType().Name == "EntityMoveRequestEventArgs")            {                if (OnEntityMoveRequestEvent != null)                {                    //LogFile.WriteLine("EventManager has received EntityMoveRequest event");                    OnEntityMoveRequestEvent(this, (EntityMoveRequestEventArgs)obj);                }            }            else if (obj.GetType().Name == "MapBuiltEventArgs")            {                if (OnMapBuiltEvent != null)                {                    //LogFile.WriteLine("EventManager has received a MapBuilt event");                    OnMapBuiltEvent(this, (MapBuiltEventArgs)obj);                }            }            else if (obj.GetType().Name == "EntityMoveEventArgs")            {                if (OnEntityMoveEvent != null)                {                    //LogFile.WriteLine("EventManager has received an EntityMove event");                    OnEntityMoveEvent(this, (EntityMoveEventArgs)obj);                }            }            else if (obj.GetType().Name == "EntityPlaceEventArgs")            {                if (OnEntityPlaceEvent != null)                {                    //LogFile.WriteLine("EventManager has received an EntityPlace event");                    OnEntityPlaceEvent(this, (EntityPlaceEventArgs)obj);                }            }        }        internal event EntityPlaceEvent OnEntityPlaceEvent;        internal event EntityMoveEvent OnEntityMoveEvent;        internal event MapBuiltEvent OnMapBuiltEvent;        internal event GameStatusEvent OnGameStatusEvent;        internal event EntityMoveRequestEvent OnEntityMoveRequestEvent;    }    class EntityPlaceEventArgs : EventArgs    {    }    delegate void EntityPlaceEvent(EventPublisher p, EntityPlaceEventArgs a);    class EntityMoveEventArgs : EventArgs    {    }    delegate void EntityMoveEvent(EventPublisher p, EntityMoveEventArgs a);    class MapBuiltEventArgs : EventArgs    {    }    delegate void MapBuiltEvent(EventPublisher p, MapBuiltEventArgs a);    class GameStatusEventArgs : EventArgs    {    }    delegate void GameStatusEvent(EventPublisher p, GameStatusEventArgs a);    class EntityMoveRequestEventArgs : EventArgs    {    }    delegate void EntityMoveRequestEvent(EventPublisher p, EntityMoveRequestEventArgs a);}


and here is the visitor pattern version
using System;namespace DynamicDispatchSample{    class Program    {        static void A(EventPublisher p, GameStatusEventArgs e)        {            Console.WriteLine("A");        }        static void B(EventPublisher p, EntityMoveRequestEventArgs e)        {            Console.WriteLine("B");        }        static void C(EventPublisher p, MapBuiltEventArgs e)        {            Console.WriteLine("C");        }        static void D(EventPublisher p, EntityMoveEventArgs e)        {            Console.WriteLine("D");        }        static void E(EventPublisher p, EntityPlaceEventArgs e)        {            Console.WriteLine("E");        }        static void Main(string[] args)        {            EventPublisher p = new EventPublisher();            p.OnGameStatusEvent += new GameStatusEvent(A);            p.OnEntityMoveRequestEvent += new EntityMoveRequestEvent(B);            p.OnMapBuiltEvent += new MapBuiltEvent(C);            p.OnEntityMoveEvent += new EntityMoveEvent(D);            p.OnEntityPlaceEvent += new EntityPlaceEvent(E);            p.Publish(new GameStatusEventArgs());            p.Publish(new EntityMoveRequestEventArgs());            p.Publish(new MapBuiltEventArgs());            p.Publish(new EntityMoveEventArgs());            p.Publish(new EntityPlaceEventArgs());        }    }    interface Acceptor    {        void Visit(EventPublisher p);    }    class EventPublisher    {        public void Accept(GameStatusEventArgs a)        {            if (OnGameStatusEvent != null)            {                OnGameStatusEvent(this, a);            }        }        public void Accept(EntityMoveRequestEventArgs a)        {            if (OnEntityMoveRequestEvent != null)            {                OnEntityMoveRequestEvent(this, a);            }        }        public void Accept(MapBuiltEventArgs a)        {            if (OnMapBuiltEvent != null)            {                OnMapBuiltEvent(this, a);            }        }        public void Accept(EntityMoveEventArgs a)        {            if (OnEntityMoveEvent != null)            {                OnEntityMoveEvent(this, a);            }        }        public void Accept(EntityPlaceEventArgs a)        {            if (OnEntityPlaceEvent != null)            {                OnEntityPlaceEvent(this, a);            }        }        public void Publish(Acceptor obj)        {            if (obj == null)            {                throw new ArgumentNullException("obj");            }            else            {                obj.Visit(this);            }        }        internal event EntityPlaceEvent OnEntityPlaceEvent;        internal event EntityMoveEvent OnEntityMoveEvent;        internal event MapBuiltEvent OnMapBuiltEvent;        internal event GameStatusEvent OnGameStatusEvent;        internal event EntityMoveRequestEvent OnEntityMoveRequestEvent;    }    class EntityPlaceEventArgs : Acceptor    {        public void Visit(EventPublisher p)        {            p.Accept(this);        }    }    delegate void EntityPlaceEvent(EventPublisher p, EntityPlaceEventArgs a);    class EntityMoveEventArgs : Acceptor    {        public void Visit(EventPublisher p)        {            p.Accept(this);        }    }    delegate void EntityMoveEvent(EventPublisher p, EntityMoveEventArgs a);    class MapBuiltEventArgs : Acceptor    {        public void Visit(EventPublisher p)        {            p.Accept(this);        }    }    delegate void MapBuiltEvent(EventPublisher p, MapBuiltEventArgs a);    class GameStatusEventArgs : Acceptor    {        public void Visit(EventPublisher p)        {            p.Accept(this);        }    }    delegate void GameStatusEvent(EventPublisher p, GameStatusEventArgs a);    class EntityMoveRequestEventArgs : Acceptor    {        public void Visit(EventPublisher p)        {            p.Accept(this);        }    }    delegate void EntityMoveRequestEvent(EventPublisher p, EntityMoveRequestEventArgs a);}

As you can see, no explicit type casts, no reflection

[Edited by - capn_midnight on June 20, 2008 9:13:46 AM]

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

Quote:Original post by benryves
From what I can see, all this achieves is having a method with a single name -- Publish() -- that calls other methods depending on the type of data it was passed. Why couldn't you do:
*** Source Snippet Removed ***This solves your type-casting concerns and also provides you with compile-time type checking, and doesn't require any reflection.


That only works if he has explict references to the EventArg types. If he is writing an event dispatching system that only ever handles argument sub-types through handles of the base EventArg type, then attempting to make a call like that will result in an ambiguous reference compile time error. The visitor pattern is necessary here.

[Formerly "capn_midnight". See some of my projects. Find me on twitter tumblr G+ Github.]

This topic is closed to new replies.

Advertisement