Leaders snarfblam Posted July 17, 2006 Leaders Posted July 17, 2006 Almost any aspect of any object can be handled dynamically through reflection. Functions can be called dynamically because we can pass parameters in an array. We can access properties and fields and instantiate classes. We can discover base classes and interfaces. But is there a way to dynamically handle events? To receive parameters as an array from an event in the same way that we would pass them to a function using reflection, or anything to that effect? I'd like to avoid is compiling and creating assembies during runtime. It seems as though it could get pretty bulky if I need to handle a very large range of event types. I will need to create them as I need them, which means that if I have 50 event types I would need to emit 50 dynamic assemblies. I suppose I could optimize by creating and saving an assembly that contains all encountered event types when the app closes (basically caching them all in a single DLL) so that next time I will have far fewer assemblies, but it still seems to be a bit much. Quote [sIGPIC]e[/sIGPIC]
Gill Bates Posted July 20, 2006 Posted July 20, 2006 It can be done: http://msdn2.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx http://www.codeproject.com/csharp/CommonEventHandler.asp Quote
MrPaul Posted July 20, 2006 Posted July 20, 2006 (edited) Heh heh, a few hours too late, but I may as well show the fruits of my labour. After reading this interesting question, I pondered about it for a while and a hairbrained solution congealed in my mind. A quick glance at the code project effort shows that it does what you wanted to avoid - dynamic code generation. My solution offers a different approach, with one caveat which I will explain afterwards. Basically, I implemented a series of methods which take between zero and three arguments (though obviously more could be implemented) all of type object. Thus, relying on the delegate contravariance introduced in C# 2.0, we can create instances of the delegate type used for the event handler, and add it to the event, without needing to know the exact delegate signature. If that all makes sense, here is the code. First up, the event sources: // Pointless classes which just raise events class RaisesEvents { public delegate void EventZeroHandler(); public delegate void EventOneHandler(string one); public delegate void EventTwoHandler(RaisesEvents one, EventArgs two); public delegate void EventThreeHandler(string one, List<int> two, double[] three); public event EventZeroHandler EventZero; public event EventOneHandler EventOne; public event EventTwoHandler EventTwo; public event EventThreeHandler EventThree; public void RaiseThemAll() { try { EventZero(); EventOne("One"); EventTwo(this, new EventArgs()); EventThree("One", new List<int>(2), new double[] {3.0f}); } catch {} } } class RaisesEventsToo { public delegate void EventZeroHandler(); public delegate void EventOneHandler(string one); public delegate void EventTwoHandler(RaisesEventsToo one, EventArgs two); public delegate void EventThreeHandler(string one, List<int> two, double[] three); public event EventZeroHandler EventZero; public event EventOneHandler EventOne; public event EventTwoHandler EventTwo; public event EventThreeHandler EventThree; public void RaiseThemAll() { try { EventZero(); EventOne("One"); EventTwo(this, new EventArgs()); EventThree("One", new List<int>(2), new double[] { 3.0f }); } catch { } } } Then, our class which can attach itself to the events of an object: class EventAttacher { static void Main(string[] args) { EventAttacher p = new EventAttacher(); RaisesEvents re = new RaisesEvents(); RaisesEventsToo re2 = new RaisesEventsToo(); p.AttachToEvents(re); p.AttachToEvents(re2); re.RaiseThemAll(); re2.RaiseThemAll(); Console.WriteLine("Done"); Console.ReadLine(); } public void HandlerZero() { Console.WriteLine("Received no parameters"); } public void HandlerOne(object one) { Console.WriteLine("Received one parameter: " + one.GetType().Name); } public void HandlerTwo(object one, object two) { Console.WriteLine("Received two parameters: " + one.GetType().Name + "; " + two.GetType().Name); } public void HandlerThree(object one, object two, object three) { Console.WriteLine("Received three parameters: " + one.GetType().Name + "; " + two.GetType().Name + "; " + three.GetType().Name); } public void AttachToEvents(object o) { Type evtype; ConstructorInfo ctorinf; MethodInfo[] methods; Delegate dg; int numargs; //Initialise method info methods = new MethodInfo[4]; methods[0] = this.GetType().GetMethod("HandlerZero"); methods[1] = this.GetType().GetMethod("HandlerOne"); methods[2] = this.GetType().GetMethod("HandlerTwo"); methods[3] = this.GetType().GetMethod("HandlerThree"); //Loop through events of o and attach foreach (EventInfo ev in o.GetType().GetEvents()) { evtype = ev.EventHandlerType; //How many arguments? numargs = evtype.GetMethod("Invoke").GetParameters().Length; //Get delegate constructor ctorinf = evtype.GetConstructor(new Type[] {typeof(object), typeof(IntPtr)}); //Create delegate with appropriate number of args dg = (Delegate) ctorinf.Invoke(new object[] { this, methods[numargs].MethodHandle.GetFunctionPointer() }); //Assign the delegate to the event if (dg == null) { Console.WriteLine("Failed to instantiate delegate for " + numargs + "-argument event"); } else { ev.AddEventHandler(o, dg); Console.WriteLine("Attached to " + numargs + "-argument event"); } } } } Ugly! Basically, the AttachToEvents method goes through the events of the target object and creates a delegate with the appropriate number of arguments and attaches it. The output for this test case looks like so: Attached to 0-argument event Attached to 1-argument event Attached to 2-argument event Attached to 3-argument event Attached to 0-argument event Attached to 1-argument event Attached to 2-argument event Attached to 3-argument event Received no parameters Received one parameter: String Received two parameters: RaisesEvents; EventArgs Received three parameters: String; List`1; Double[] Received no parameters Received one parameter: String Received two parameters: RaisesEventsToo; EventArgs Received three parameters: String; List`1; Double[] Done Now, to the caveat - the events you attach to must only take reference type arguments. Value types do not work with this method. Hope it all makes sense, and is of some use. :cool: Edited July 20, 2006 by MrPaul Quote Never trouble another for what you can do for yourself.
Leaders snarfblam Posted July 20, 2006 Author Leaders Posted July 20, 2006 Well, like I said, I would prefer not to create an assembly for each type of event (both of the links do this), but I don't think it can be helped. Mr Paul, each line of code is clearly commented but it is hard to make out the logic of the code as a whole. By the looks of things, though, I would say that when using your method, the event handlers must already be defined for each given number of arguments. Also, I think there would be a problem with your code when pass-by-reference arguments are involved. And value types are a must. Basically, I want to be able to handle just about anything that a user might try to link to dynamically. That means anything goes. Not just sender/eventargs events, but any kind of signature. Your code looks handy and dynamic, but it doesn't quite fit the bill. Quote [sIGPIC]e[/sIGPIC]
Gill Bates Posted July 21, 2006 Posted July 21, 2006 I don't think you need to have separate assemblies. You just use the AssemblyBuilder class:AssemblyName assemblyName = new AssemblyName(); assemblyName.Name = "DynamicEvents"; AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);You can then call the "DefineDynamicModule" on the AssemblyBuilder instance and start building as many types (dynamically) as you like. Quote
MrPaul Posted July 21, 2006 Posted July 21, 2006 Mr Paul' date=' each line of code is clearly commented but it is hard to make out the logic of the code as a whole. By the looks of things, though, I would say that when using your method, the event handlers must already be defined for each given number of arguments. Also, I think there would be a problem with your code when pass-by-reference arguments are involved. And value types are a must. Basically, I want to be able to handle just about anything that a user might try to link to dynamically. That means anything goes. Not just sender/eventargs events, but any kind of signature. Your code looks handy and dynamic, but it doesn't quite fit the bill.[/quote'] Yes, you are correct in that every event handler method must be explicitly declared, so if you predict you might need to handle events taking up to 20 parameters, then you'd need to define 21 handlers, which is not ideal. And you quite rightly note that it would not work with ref or out parameters, or value types, so its use is extremely limited. Although it will clearly not be that helpful to you, I might as well explain what it is doing. Basically it finds the number of arguments the event takes by counting the parameters of the Invoke method of the event's delegate, which always matches the delegate's signature. It then selects one of our predefined handler methods with a matching number of arguments, and creates a Delegate object from that method. Every delegate has a constructor which takes an object and an IntPtr (to the method body). We pass in the object containing the method (current instance of EventAttacher) and the pointer to the appropriate method, which can be retrieved from the MethodInfo. The constructed delegate is then attached to the event. Good luck :cool: Quote Never trouble another for what you can do for yourself.
Gill Bates Posted July 21, 2006 Posted July 21, 2006 Yes' date=' you are correct in that every event handler method must be explicitly declared, so if you predict you might need to handle events taking up to 20 parameters, then you'd need to define 21 handlers, which is not ideal. And you quite rightly note that it would not work with ref or out parameters, or value types, so its use is extremely limited.[/quote']Not true. Here's some code that will dynamically create a new type that is subscribed to another type's events:static Type BuildDyanmicType(AssemblyBuilder ab, string assemblyName, Type sourceType) { ModuleBuilder mb = ab.DefineDynamicModule(assemblyName); TypeBuilder tb = mb.DefineType(string.Format("{0}DynamicHandler", sourceType.Name), TypeAttributes.Class | TypeAttributes.Public); // build a method to handle each event in the source type foreach (EventInfo eventInfo in sourceType.GetEvents()) { MethodInfo method = eventInfo.EventHandlerType.GetMethod("Invoke"); List<Type> parameterTypes = new List<Type>(); foreach (ParameterInfo parameter in method.GetParameters()) { parameterTypes.Add(parameter.ParameterType); } MethodBuilder handler = tb.DefineMethod(string.Format("{0}_Handler", eventInfo.Name), MethodAttributes.Public | MethodAttributes.Static, method.ReturnType, parameterTypes.ToArray()); ILGenerator il = handler.GetILGenerator(); il.EmitWriteLine("The event has been raised."); il.Emit(OpCodes.Ret); } return tb.CreateType(); } Quote
MrPaul Posted July 21, 2006 Posted July 21, 2006 (edited) Yes that's all very nice, but I was explaining the construction of code I posted previously, as you may notice if you read the text I quoted. Of course your 'Dyanmic' types approach (or rather, the approach used at the code project) is superior. :-\ Edited July 21, 2006 by MrPaul Quote Never trouble another for what you can do for yourself.
Leaders snarfblam Posted July 21, 2006 Author Leaders Posted July 21, 2006 (edited) Okay, let me make this clear. No offense, but no one seems to be reading the whole thread. Using dynamic assemblies means that I need to create new assemblies at run-time. Since I will need to create and access handlers immediately as I need them I will need to finalize each and every dynamic delegate I create, in its own assembly, hence an assembly for each type of event. This is what I want to avoid. You can't go back and amend a dynamic assembly once the process is finished, and I will need to finish the process for every delegate type because it needs to be created and used as soon as it is defined (I have no way of knowing ahead of time which types of delegates I will need). MrPaul came up with an ingenious bit of code that can use pre-defined delegates and handlers to handle 95% of the events I will run into, but 95% isn't enough. I think that a hybrid solution would be my best bet. Predefine the very common event handlers. For the rest, create dynamic assemblies. But, on application close, create a single new dynamic assembly where I can consolidate all dynamically created event handlers, and save this to a DLL. Next time I load the app I will have a single assembly that contains any previously-created handlers. Correction: You can use the DynamicMethod class to generate and execute a method at run time, without having to generate a dynamic assembly and a dynamic type to contain the method. Dynamic methods are the most efficient way to generate and execute small amounts of code.[/Quote] Edited July 21, 2006 by snarfblam Quote [sIGPIC]e[/sIGPIC]
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.