I have a bunch of generic events I want to subscribe to and make them all call one non-generic method. Here's my code:
public delegate void PropertyChangedDelegate<OwnerType, PropertyType>(OwnerType sender, PropertyType oldValue, PropertyType newValue);
public class EventObject
{
public event PropertyChangedDelegate<Object, Boolean> PropertyChanged;
public event PropertyChangedDelegate<Object, Int32> XChanged;
}
static void Main()
{
EventObject eventObject = new EventObject();
EventInfo eventInfo = eventObject.GetType().GetEvent("PropertyChanged");
eventInfo.AddEventHandler(eventObject, PropertyChanged);
}
static void PropertyChanged(Object obj, Object oldValue, Object newValue)
{
}
Obviously this doesn't work, is there any way to do a wrapper generic method?
The issue is that PropertyChanged method is not contravariant to the PropertyChangedDelegate type because sending bool as object require boxing, so it is clear that you cannot make delegate to work universally with all events. The solution is to write a static method as a "landing method". Here is my solution:
using System;
using System.Reflection;
public delegate void PropertyChangedDelegate<OwnerType, PropertyType>(OwnerType sender, PropertyType oldValue, PropertyType newValue);
public class EventObject
{
public event PropertyChangedDelegate<Object, Boolean> PropertyChanged;
public event PropertyChangedDelegate<Object, Int32> XChanged;
public void RaisePropertyChanged() {
PropertyChanged(this, true, false);
}
}
class Test {
static void Main()
{
EventObject eventObject = new EventObject();
EventInfo eventInfo = eventObject.GetType().GetEvent("PropertyChanged");
Type evType = eventInfo.EventHandlerType;
// replace below with this.GetType() in case of instance method
Type thisType = typeof(Test);
MethodInfo mi = thisType.GetMethod("PropertyChanged", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
MethodInfo genericMi = mi.MakeGenericMethod(evType.GetGenericArguments());
Delegate del = Delegate.CreateDelegate(evType, genericMi);
eventInfo.AddEventHandler(eventObject, del);
// Test
eventObject.RaisePropertyChanged();
}
static void PropertyChanged<TOwner, T>(TOwner obj, T oldValue, T newValue)
{
Console.WriteLine("-- Invoked --");
}
}
Inspired from
Using Reflection to get static method with its parameters
and
Create generic delegate using reflection
You can't pass a method group as such in place of a delegate. You can specify the exact delegate your method matches to, like this:
EventObject eventObject = new EventObject();
EventInfo eventInfo = eventObject.GetType().GetEvent("PropertyChanged");
eventInfo.AddEventHandler(eventObject, (Action<Object, Object, Object>)PropertyChanged);
But it still would give you runtime exception since the signatures don't match.
Why not the straightforward += implementation?
EventObject eventObject = new EventObject();
eventObject.PropertyChanged += PropertyChanged;
But this wont compile because of type mismatch in signature. You have to either change signature of delegate
public delegate void PropertyChangedDelegate(Object sender, Object oldValue, Object newValue);
or change signature of event
public event PropertyChangedDelegate<Object, Object> PropertyChanged;
(but that spoils all your efforts to have a strongly typed delegate) or change signature of method
static void PropertyChanged(Object obj, Boolean oldValue, Boolean newValue)
{
}
which is what I would go with.
Related
how to add a event handler myhandler using reflection if I only have the type AType of the event thrower?
Delegate myhandler = SomeHandler;
EventInfo info= AType.GetEvent("CollectionChanged");
info.AddEventHandler( ObjectOfAType, myhandler )
Basically, what you have is fine. The only glitch is that myhandler actually needs to be of the correct type, which is to say: of the type defined by the event.
For example:
using System;
using System.Collections.Specialized;
using System.Reflection;
class Program
{
static void Main()
{
Type AType = typeof(Foo);
var ObjectOfAType = new Foo();
Delegate myhandler = (NotifyCollectionChangedEventHandler)SomeHandler;
EventInfo info = AType.GetEvent("CollectionChanged");
info.AddEventHandler(ObjectOfAType, myhandler);
// prove it works
ObjectOfAType.OnCollectionChanged();
}
private static void SomeHandler(object sender, NotifyCollectionChangedEventArgs e)
=> Console.WriteLine("Handler invoked");
}
public class Foo
{
public void OnCollectionChanged() => CollectionChanged?.Invoke(this, null);
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
There is a Delegate.CreateDelegate() method that takes a delegate type, an object instance, and a MethodInfo method; you can use this (using the event-type from EventInfo info) to create an appropriate delegate instance for a given method on a particular object.
public class AwaitableRoutedEvent
{
private TaskCompletionSource<object> _tcs;
public async Task When<TypeOfControl>(TypeOfControl source, string nameOfEvent)
{
_tcs = new TaskCompletionSource<object>();
var targetEventInfo = source.GetType().GetEvent(nameOfEvent);
Delegate tempEventHandler =
Delegate.CreateDelegate(
targetEventInfo.EventHandlerType,
GetType().GetMethod(nameof(RoutedEventHandler), BindingFlags.Instance | BindingFlags.Public));
try
{
targetEventInfo.AddEventHandler(source, tempEventHandler);
await _tcs.Task;
}
finally
{
targetEventInfo.RemoveEventHandler(source, tempEventHandler);
_tcs = null;
}
}
public void RoutedEventHandler(object sender, RoutedEventArgs arguments)
{
_tcs.TrySetResult(null);
}
}
The calss above represents an 'awaitable' RoutedEventHandler event. The goal is to use it like await new AwaitableRoutedEvent().When(button_object, "click");.
IF I MAKE public void RoutedEventHandler(object sender, RoutedEventArgs arguments) STATIC... and change BindingsFlags... it works. But I don't like static. Non-static version throws an exception: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
Did I miss something?
Because RoutedEventHandler is an instance method, when creating a delegate you should supply an instance of the object on which the method is invoked:
Delegate tempEventHandler =
Delegate.CreateDelegate(
targetEventInfo.EventHandlerType,
this,
GetType().GetMethod(nameof(RoutedEventHandler), BindingFlags.Instance | BindingFlags.Public));
And seemingly you do not need that at all because you may write
RoutedEventHandler tempEventHandler = this.MyRoutedEventhander;
public void MyRoutedEventHandler(object sender, RoutedEventArgs arguments)
{
_tcs.TrySetResult(null);
}
I'm trying to create a reusable method for a more complicated event execution. I can't get it to compile or run with framework events that don't follow the EventHandler<Type> pattern. I would like to avoid reflection if possible as it will be a heavily used event.
I've created a test console app below which illustrates the problem:
using System;
using System.Collections.Specialized;
namespace CallEventsViaMethod
{
public class TestEventArgs : EventArgs { }
class Program
{
static void Main(string[] args)
{
MyProgram program = new MyProgram();
program.Go();
Console.ReadKey(false);
}
}
public class MyProgram
{
public event EventHandler<TestEventArgs> TestEvent;
public event NotifyCollectionChangedEventHandler CollectionChangedEvent;
public void Go()
{
TestEvent += new EventHandler<TestEventArgs>(MyProgram_TestEvent);
CollectionChangedEvent += new NotifyCollectionChangedEventHandler(MyProgram_CollectionChangedEvent);
// Want a reusable method I can use to conditionally execute any event
GeneralEventExecutor.Execute<TestEventArgs>(TestEvent, new Object(), new TestEventArgs());
GeneralEventExecutor.Execute<NotifyCollectionChangedEventArgs>(TestEvent, new Object(), new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
void MyProgram_TestEvent(object arg1, TestEventArgs arg2)
{
Console.WriteLine("Custom event ran");
}
void MyProgram_CollectionChangedEvent(object sender, NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("NotifyCollectionChangedEventHandler event ran");
}
}
public static class GeneralEventExecutor
{
public static void Execute<T>(EventHandler<T> eventToRaise, object sender, T eventArgs) where T : EventArgs
{
if (eventToRaise == null)
return;
Delegate[] registeredEventHandlers = eventToRaise.GetInvocationList();
foreach (EventHandler<T> eventHandler in registeredEventHandlers)
{
object target = eventHandler.Target; // Need access to the Target property
// * Code deciding if should invoke the event handler *
eventHandler.Invoke(sender, eventArgs);
}
}
}
}
Error messages are:
error CS1502: The best overloaded method match for
'CallEventsViaMethod.GeneralEventExecutor.Execute(System.EventHandler,
object,
System.Collections.Specialized.NotifyCollectionChangedEventArgs)' has
some invalid arguments
error CS1503: Argument 1: cannot convert from
'System.EventHandler' to
'System.EventHandler'
I understand why I'm getting the error, but can't figure out a way round it.
Replace your your generic Execute<T> as below
public static void Execute<T>(Delegate eventToRaise, object sender, T eventArgs) where T:EventArgs
{
if (eventToRaise == null)
return;
Delegate[] registeredEventHandlers = eventToRaise.GetInvocationList();
foreach (Delegate eventHandler in registeredEventHandlers)
{
object target = eventHandler.Target; // Need access to the Target property for conditions
// * Code deciding if should invoke the event handler *
eventHandler.DynamicInvoke(sender, eventArgs);
}
}
I know about EventInfo.AddEventHandler(...) method which can be used to attach handler to an event. But what should be done if i can not even define proper signature of the event handler, as in, i don't even have reference to the event args expected by the handler?
I will explain the problem with the proper code.
// Scenario when I have everything available in my solution, Zero Reflection Scenario.
internal class SendCommentsManager
{
public void Customize(IRFQWindowManager rfqWindowManager)
{
rfqWindowManager.SendComment += HandleRfqSendComment;
}
private void HandleRfqSendComment(object sender, SendCommentEventArgs args)
{
args.Cancel = true;
}
}
Now, I want to achieve the same objective by using reflection. I have been able to figure out most of it but when i attach a delegate to the event (using AddEventHandler) it throws "Error binding to target method." exception.
I understand the reason behind this exception, attaching a wrong delegate to an event. But there must be some way to achieve this.
internal class SendCommentsManagerUsingReflection
{
public void Customize(IRFQWindowManager rfqWindowManager)
{
EventInfo eventInfo = rfqWindowManager.GetType().GetEvent("SendComment");
eventInfo.AddEventHandler(rfqWindowManager,
Delegate.CreateDelegate(eventInfo.EventHandlerType, this, "HandleRfqSendComment"));
//<<<<<<<<<<ABOVE LINE IS WHERE I AM GOING WRONG>>>>>>>>>>>>>>
}
private void HandleRfqSendComment(object sender, object args)
{
Type sendCommentArgsType = args.GetType();
PropertyInfo cancelProperty = sendCommentArgsType.GetProperty("Cancel");
cancelProperty.SetValue(args, true, null);
}
}
I think your code is failing because the HandleRfqSendComment is private. Instead you could directly create a delegate to that method, without passing its name to CreateDelegate. You would then need to convert the delegate to the required type, using the following method :
public static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
{
return Delegate.CreateDelegate(
targetDelegateType,
originalDelegate.Target,
originalDelegate.Method);
}
In your code, you could use this method as follows :
EventInfo eventInfo = rfqWindowManager.GetType().GetEvent("SendComment");
Action<object, object> handler = HandleRfqSendComment;
Delegate convertedHandler = ConvertDelegate(handler, eventInfo.EventHandlerType);
eventInfo.AddEventHandler(rfqWindowManager, convertedHandler);
A small addition to the already awesome answers here. Here's a helper class you could use to subscribe to events with actions.
public static partial class ReactiveExtensions
{
public static EventHandler<TEvent> CreateGenericHandler<TEvent>(object target, MethodInfo method)
{
return (EventHandler<TEvent>)Delegate
.CreateDelegate(typeof(EventHandler<TEvent>),
target, method);
}
public static EventHandler CreateHandler(object target, MethodInfo method)
{
return (EventHandler)Delegate
.CreateDelegate(typeof(EventHandler),
target, method);
}
static void BindEventToAction(object target, EventInfo eventInfo, Delegate action)
{
MethodInfo method;
if (eventInfo.EventHandlerType.IsGenericType)
{
method = typeof(ReactiveExtensions)
.GetMethod(nameof(CreateGenericHandler))
.MakeGenericMethod(
eventInfo.EventHandlerType.GetGenericArguments());
}
else
{
method = typeof(ReactiveExtensions)
.GetMethod(nameof(CreateHandler));
}
Delegate #delegate = (Delegate)method.Invoke(null,
new object[] { action.Target, action.Method });
eventInfo.AddEventHandler(target, #delegate);
}
}
Here's a sample on how to use this:
public static partial class ReactiveExtensions
{
public static void Subscribe<T>(T source, string eventName)
{
EventInfo eventInfo = typeof(T).GetEvent(eventName);
Action<object, object> action = (s, e) =>
{
Console.WriteLine("Event Called");
};
BindEventToAction(source, eventInfo, action);
}
}
I have this piece of code that does not work:
public CartaoCidadao()
{
InitializeComponent();
object o = WebDAV.Classes.SCWatcher.LoadAssembly();
MethodInfo method =
this.GetType().GetMethod("Inserted",
BindingFlags.NonPublic | BindingFlags.Instance);
EventInfo eventInfo = o.GetType().GetEvent("CardInserted");
Type type = eventInfo.EventHandlerType;
Delegate handler = Delegate.CreateDelegate(type, this , method);
eventInfo.AddEventHandler(o, handler);
}
void Inserted(string readerName, string cardName)
{
System.Windows.Forms.MessageBox.Show(readerName);
}
The Event CardInserted exists in another DLL file and object "o" loads OK. The delegate handler has a value after effect. I only can't fire the event.
Here's a sample showing how to attach an event using reflection:
class Program
{
static void Main(string[] args)
{
var p = new Program();
var eventInfo = p.GetType().GetEvent("TestEvent");
var methodInfo = p.GetType().GetMethod("TestMethod");
Delegate handler =
Delegate.CreateDelegate(eventInfo.EventHandlerType,
p,
methodInfo);
eventInfo.AddEventHandler(p, handler);
p.Test();
}
public event Func<string> TestEvent;
public string TestMethod()
{
return "Hello World";
}
public void Test()
{
if (TestEvent != null)
{
Console.WriteLine(TestEvent());
}
}
}
Here's a short but complete example which does work:
using System;
using System.Reflection;
class EventPublisher
{
public event EventHandler TestEvent;
public void RaiseEvent()
{
TestEvent(this, EventArgs.Empty);
}
}
class Test
{
void HandleEvent(object sender, EventArgs e)
{
Console.WriteLine("HandleEvent called");
}
static void Main()
{
// Find the handler method
Test test = new Test();
EventPublisher publisher = new EventPublisher();
MethodInfo method = typeof(Test).GetMethod
("HandleEvent", BindingFlags.NonPublic | BindingFlags.Instance);
// Subscribe to the event
EventInfo eventInfo = typeof(EventPublisher).GetEvent("TestEvent");
Type type = eventInfo.EventHandlerType;
Delegate handler = Delegate.CreateDelegate(type, test, method);
// Raise the event
eventInfo.AddEventHandler(publisher, handler);
publisher.RaiseEvent();
}
}
Now, when you say "I only can't fire event", what exactly do you mean? You're not meant to be able to raise events yourself - it's up to the event publisher to do that. Does all of the code you've actually presented to us work? If so, it seems that it's not adding the event handler that's the problem.
Could you give more information?
When you say it doesn't work... what happens? Nothing? An exception?
Thoughts:
are both the event and the handler public? If not, you'll need to pass suitable BindingFlags to the GetEvent / GetMethod calls.
does the signature of the handler match?
Here's a working example (note I'm using a static handler, hence the null in Delegate.CreateDelegate):
using System;
using System.Reflection;
class Test
{
public event EventHandler SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null) SomeEvent(this, EventArgs.Empty);
}
static void Main()
{
Test obj = new Test();
EventInfo evt = obj.GetType().GetEvent("SomeEvent");
MethodInfo handler = typeof(Test)
.GetMethod("MyHandler");
Delegate del = Delegate.CreateDelegate(
evt.EventHandlerType, null, handler);
evt.AddEventHandler(obj, del);
obj.OnSomeEvent();
}
public static void MyHandler(object sender, EventArgs args)
{
Console.WriteLine("hi");
}
}