Add and remove event handler via reflection c# - c#

Good day!
My purpose is to implement class which will allow us subscribe and unsubscribe objects to(from) events. Here is the code of my class.
public static class EventSubscriber
{
public static void AddEventHandler(EventInfo eventInfo, object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.AddEventHandler(item, handler);
}
public static void RemoveEventHandler(EventInfo eventInfo,
object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.RemoveEventHandler(item, handler);
}
private static ParameterExpression[] GetParameters(EventInfo eventInfo)
{
return eventInfo.EventHandlerType
.GetMethod("Invoke")
.GetParameters()
.Select(parameter => Expression.Parameter(parameter.ParameterType))
.ToArray();
}
private static Delegate GetHandler(EventInfo eventInfo,
Action action, ParameterExpression[] parameters)
{
return Expression.Lambda(
eventInfo.EventHandlerType,
Expression.Call(Expression.Constant(action),
"Invoke", Type.EmptyTypes), parameters)
.Compile();
}
}
As you can see here are 2 public methods which actually subscribe and unsubscribe objects to(from) event. And here is the sample how I test it
class Program
{
static void Main()
{
Test test = new Test();
test.SubscribeTimer();
while (true)
{
if(test.a == 10)
{
break;
}
}
test.UnsubscribeTimer();
while (true)
{
}
}
}
class Test
{
System.Timers.Timer timer;
public int a = 0;
public Test()
{
timer = new System.Timers.Timer(1000);
timer.Start();
}
public void SubscribeTimer()
{
var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed");
EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerNotElapsed);
}
public void UnsubscribeTimer()
{
var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed");
EventSubscriber.AddEventHandler(eventInfo, timer, TimerNotElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerElapsed);
}
public void TimerElapsed()
{
Console.WriteLine("timer elapsed");
a++;
}
public void TimerNotElapsed()
{
Console.WriteLine("timer not elapsed");
a++;
}
}
The expected behaviour of sample is that on the begining we will see the message "timer elapsed" every second, after 10-th second we should see only "timer not elapsed" and we do, but we still see "timer elapsed" too. This means that AddEventHandler method works, but RemoveEventHandler method doesn't.
I would be very happy if you will help me. Thanks in advance.

Had to stitch a bunch of sources together to get what I needed which was a one-stop extension method (that could be easily modified if we switched component vendors) to simply clear all existing Event Handlers on an Event we did not control.
Usage is simple:
thirdPartyControlInstance.ClearEventHandlers(nameof(ThirdPartyControlType.ToolClick));
Here is the code, any suggestions to make it more robust/efficient/cleaner are welcome:
public static class EventExtensions
{
public static void ClearEventHandlers(this object obj, string eventName)
{
if (obj == null)
{
return;
}
var objType = obj.GetType();
var eventInfo = objType.GetEvent(eventName);
if (eventInfo == null)
{
return;
}
var isEventProperty = false;
var type = objType;
FieldInfo eventFieldInfo = null;
while (type != null)
{
/* Find events defined as field */
eventFieldInfo = type.GetField(eventName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (eventFieldInfo != null && (eventFieldInfo.FieldType == typeof(MulticastDelegate) || eventFieldInfo.FieldType.IsSubclassOf(typeof(MulticastDelegate))))
{
break;
}
/* Find events defined as property { add; remove; } */
eventFieldInfo = type.GetField("EVENT_" + eventName.ToUpper(), BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
if (eventFieldInfo != null)
{
isEventProperty = true;
break;
}
type = type.BaseType;
}
if (eventFieldInfo == null)
{
return;
}
if (isEventProperty)
{
// Default Events Collection Type
RemoveHandler<EventHandlerList>(obj, eventFieldInfo);
// Infragistics Events Collection Type
RemoveHandler<EventHandlerDictionary>(obj, eventFieldInfo);
return;
}
if (!(eventFieldInfo.GetValue(obj) is Delegate eventDelegate))
{
return;
}
// Remove Field based event handlers
foreach (var d in eventDelegate.GetInvocationList())
{
eventInfo.RemoveEventHandler(obj, d);
}
}
private static void RemoveHandler<T>(object obj, FieldInfo eventFieldInfo)
{
var objType = obj.GetType();
var eventPropertyValue = eventFieldInfo.GetValue(obj);
if (eventPropertyValue == null)
{
return;
}
var propertyInfo = objType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(p => p.Name == "Events" && p.PropertyType == typeof(T));
if (propertyInfo == null)
{
return;
}
var eventList = propertyInfo?.GetValue(obj, null);
switch (eventList) {
case null:
return;
case EventHandlerDictionary typedEventList:
typedEventList.RemoveHandler(eventPropertyValue, typedEventList[eventPropertyValue]);
break;
}
}
}
Hope this helps someone!

I think it's because you are creating a new handler each time: (which doesn't match the previous handler, so can't be removed from the invocation list)
public static void RemoveEventHandler(EventInfo eventInfo,
object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters); // <--
eventInfo.RemoveEventHandler(item, handler);
}
Why are you wrapping the Action? To lose the parameters? It is not possible to add/remove the eventInfo.RemoveEventHandler(item, action); because of the parameters. If you want to remove a newly generated handler, you should return that handler when you want to remove it.
public static Delegate AddEventHandler(EventInfo eventInfo, object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.AddEventHandler(item, handler);
return handler;
}
public static void RemoveEventHandler(EventInfo eventInfo,
object item, Delegate handler)
{
eventInfo.RemoveEventHandler(item, handler);
}
var handler = EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, handler);

Related

How to call method with EventHandler parameter using expression trees?

Consider this simple piece of code. How can this be done using expression trees?
ErrorsChangedEventManager.AddHandler(obj, obj.SomeHandler);
Here's a small sample illustrating what I'm trying to accomplish. (Add a reference to WindowBase to make it compile.)
class Program : INotifyDataErrorInfo
{
public int Id { get; set; }
static void Main(string[] args)
{
var p1 = new Program { Id = 1 };
var p2 = new Program { Id = 2 };
// Here is the root of the problem.
// I need to do this INSIDE the expression from a given instance of Program.
EventHandler<DataErrorsChangedEventArgs> handler = p1.OnError;
var handlerConstant = Expression.Constant(handler);
var mi = typeof(ErrorsChangedEventManager).GetMethod(nameof(ErrorsChangedEventManager.AddHandler),
BindingFlags.Public | BindingFlags.Static);
var source = Expression.Parameter(typeof(INotifyDataErrorInfo), "source");
var program = Expression.Parameter(typeof(Program), "program");
// This will work, but the OnError method will be invoked on the wrong instance.
// So, I need to get the expression to perform what would otherwise be easy in code...
// E.g. AddHandler(someObject, p2.OnError);
var call = Expression.Call(mi, source, handlerConstant);
var expr = Expression.Lambda<Action<INotifyDataErrorInfo, Program>>(call, source, program);
var action = expr.Compile();
action.DynamicInvoke(p1, p2);
p1.ErrorsChanged.Invoke(p1, new DataErrorsChangedEventArgs("Foo"));
}
void OnError(object sender, DataErrorsChangedEventArgs e)
{
if (sender is Program p)
{
Console.WriteLine($"OnError called for Id={Id}. Expected Id=2");
}
}
public IEnumerable GetErrors(string propertyName) => Enumerable.Empty<string>();
public bool HasErrors => false;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
Obviously, it doesn't work. I somehow need to provide the OnError handler as a parameter to the call.
It seems the easiest thing to do is to create a lambda which creates the EventHandler<DataErrorsChangedEventArgs> for you, and then use Expression.Invoke to call it:
public class Program : INotifyDataErrorInfo
{
public int Id { get; set; }
public static void Main()
{
var p1 = new Program { Id = 1 };
var p2 = new Program { Id = 2 };
var mi = typeof(ErrorsChangedEventManager).GetMethod(nameof(ErrorsChangedEventManager.AddHandler),
BindingFlags.Public | BindingFlags.Static);
var source = Expression.Parameter(typeof(INotifyDataErrorInfo), "source");
var program = Expression.Parameter(typeof(Program), "program");
Expression<Func<Program, EventHandler<DataErrorsChangedEventArgs>>> createDelegate = p => p.OnError;
var createDelegateInvoke = Expression.Invoke(createDelegate, program);
var call = Expression.Call(mi, source, createDelegateInvoke);
var expr = Expression.Lambda<Action<INotifyDataErrorInfo, Program>>(call, source, program);
var action = expr.Compile();
action(p1, p2);
p1.ErrorsChanged.Invoke(p1, new DataErrorsChangedEventArgs("Foo"));
}
public void OnError(object sender, DataErrorsChangedEventArgs e)
{
if (sender is Program p)
{
Console.WriteLine($"OnError called for Id={Id}. Expected Id=2");
}
}
public IEnumerable GetErrors(string propertyName) => Enumerable.Empty<string>();
public bool HasErrors => false;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
If you look at the DebugView for createDelegate, you can see that the compiler created:
.Lambda #Lambda1<System.Func`2[Program,System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]]>(Program $p)
{
(System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]).Call .Constant<System.Reflection.MethodInfo>(Void OnError(System.Object, System.ComponentModel.DataErrorsChangedEventArgs)).CreateDelegate(
.Constant<System.Type>(System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]),
$p)
}
You could construct this expression yourself if you wanted, by getting the MethodInfo for OnError, then calling CreateDelegate on it.
All of that said, you can just use a lambda to do all of that:
Expression<Action<INotifyDataErrorInfo, Program>> test = (source, program) =>
ErrorsChangedEventManager.AddHandler(source, program.OnError);

Simple event system, can't remove listeners from list

I made a simple event system by following a tutorial, the registration of listeners and firing events works well, but I can't remove any listener from it.
delegate void EventListener(EventInfoBase eventInfo);
Dictionary<System.Type, List<EventListener>> eventListeners;
public void RegisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
eventListeners = new Dictionary<System.Type, List<EventListener>>();
}
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
eventListeners[eventType] = new List<EventListener>();
}
EventListener wrapper = (ei) => { listener((T)ei); };
eventListeners[eventType].Add(wrapper);
}
public void UnregisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
return;
}
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
return;
}
EventListener wrapper = (ei) => { listener((T)ei); };
EventListener toRemove = eventListeners[eventType].Find(x => x.Equals(wrapper));
//EventListener toRemove = eventListeners[eventType].Find(x => x.Target == wrapper.Target && x.Method == wrapper.Method);
if (toRemove != null)
{
eventListeners[eventType].Remove(toRemove); // Never gets called
}
}
This is how it's called (it's a singleton):
EventsSystem.Instance.RegisterListener<EventInfoWin>(OnWin);
EventsSystem.Instance.UnregisterListener<EventInfoWin>(OnWin);
So I expected the listener to be removed from appropriate list, but it stays there. The UnregisterListener method does nothing. Any way to fix it quickly without rewriting everything?
Your problem is that in RegisterListener<T> you create an anonymous wrapper method of type EventListener and in UnregisterListener<T> you create another wrapper method. These wrappers will never match.
What you can do is to store the original listener along with the wrapper. That will allow you to match with the original listener, but execute the wrapper (to execute the original listener you would need reflection - hence the wrapper). If you switch from a dictionary to a list of tuples, you can do this in a straightforward way:
delegate void EventListener(object eventInfo);
List<(System.Type Type, Delegate Listener, EventListener Wrapper)> eventListeners;
public void RegisterListener<T>(System.Action<T> listener)
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
eventListeners = new List<(System.Type, Delegate, EventListener)>();
}
if (!eventListeners.Any(entry => entry.Type.Equals(eventType) &&
entry.Listener.Equals(listener))) {
eventListeners.Add((eventType, listener, ei => listener((T)ei)));
}
}
public void UnregisterListener<T>(System.Action<T> listener)
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
return;
}
var toRemove = eventListeners.FirstOrDefault(entry => entry.Type.Equals(eventType) &&
entry.Listener.Equals(listener));
eventListeners.Remove(toRemove);
}
You can try it out here.
You cannot use the wrapper delegate as you do. the reason is, that this is creating another "object" on adding, which will not be be recognizable later when you want to remove it.
As Jon Skeet wrote, you can just save the action directly, but as an object. I've tested it, and I did not find a way to have an list with Action of EventInfoBase putting in an Action of EventInfoWin.
So that is what it could look like:
EDIT: I've created a wrapper again, but with the original action as a token to find it again.
delegate void EventListener(EventInfoBase eventInfo);
private class EventWrapper
{
public EventListener Action { get; set; }
public object Token { get; set; }
}
Dictionary<System.Type, List<EventWrapper>> eventListeners = new Dictionary<System.Type, List<EventWrapper>>();
public void RegisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
eventListeners[eventType] = new List<EventWrapper>();
}
EventListener action = (ei) => { listener((T)ei); };
var wrapper = new EventWrapper() { Action = action, Token = listener };
eventListeners[eventType].Add(wrapper);
}
public void UnregisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
return;
}
var toRemove = eventListeners[eventType].FirstOrDefault(x => x.Token.Equals(listener));
if (toRemove != null)
{
eventListeners[eventType].Remove(toRemove);
}
}

How to get the interface Methodinfo, based on concrete implementation's MethodInfo?

I have an interface IClass:
public interface IClass
{
[MyAttribute]
void Hello();
[MyAttribute]
void Farewell();
}
And an implementation Class1:
public class Class1 : IClass
{
public void Hello() { Console.WriteLine("Hi there") };
void IClass.Farewell() { Console.WriteLine("Goodbye!") };
}
I have a handle on Class1's MethodInfo's for Hello() and IClass.Farewell() by means of interception, but I need to find the MethodInfo of the interface definition in order to discover the usage of the attribute MyAttribute; something like this:
MethodInfo mi = context.Descriptior.CurrentMethod;
MethodInfo imi = GetInterfaceMethodInfo(mi) // ???
var mas = GetCustomAttributes<MyAttribute>();
if (mas.Count > 0)
{
// Profit!
}
EDIT:
A point I may have failed to highlight properly: I'm getting the initial MethodInfo instance from an interceptor, so I'm trying to find not only the interface's method, but I need to determine if there even is an interface in play, and if so I need to find its method declaration that corresponds to the called method.
I have a workaround that covers my current situation. On the plus side: it works. The downside: It uses string-comparisons, which I find are inherently presumptive.
private bool HasAttribute(MethodInfo methodInfo)
{
var type = methodInfo.DeclaringType;
var interfaces = type.GetInterfaces();
foreach (var #interface in interfaces)
{
var iMethodInfo = #interface.GetMethods()
.SingleOrDefault(x =>
(
methodInfo.Name.EndsWith($".{#interface.Name}.{x.Name}") || methodInfo.Name.Equals(x.Name)
)
&& IsSignatureMatch(x, methodInfo)
);
if (iMethodInfo != null)
{
Console.WriteLine(iMethodInfo.Name);
if (iMethodInfo.GetCustomAttribute<MyAttribute>() != null)
{
return true;
}
}
}
return false;
}
private bool IsSignatureMatch(MethodInfo methodInfoA, MethodInfo methodInfoB)
{
if (methodInfoA.ReturnType != methodInfoB.ReturnType)
{
return false;
}
var a = methodInfoA.GetParameters().Select(x => x.ParameterType).ToList();
var b = methodInfoB.GetParameters().Select(x => x.ParameterType).ToList();
if (a.Count != b.Count)
{
return false;
}
for (var i = 0; i < a.Count; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}

Background thread lambda callback

At the moment when my background thread is complete it runs a System.Action callback using an anonymous lambda statement. I'm wondering how can I rewrite the code below to have a callback/lambda statement using a Completed function.
ViewModel : BaseViewModel
public override void Initialize(System.Action onInitializeCallback = null)
{.....
BackgroundEntityWorker.RunWorkerCompleted += (sender, args) =>
{
If (onInitializeCallback != null)
{
onInitializeCallback();
}
};
.....
}
I want to change it to something below
BackgroundEntityWorker.RunWorkerCompleted += BackgroundWorker_Completed =>
{
If (onInitializeCallback != null)
{
onInitializeCallback();
}
};
BaseViewModel
public virtual void Reload(int? id = null, Action<T> callback = null)
{
Initialize(() =>
{
Localize();
if (id == null)
{
IndicateLoading(false);
}
else
{
Load(id.Value, () => IndicateLoading(false));
}
});
}
I get an error Argument type 'Lambda expression is not assignable to parameter type System.ComponentModel.RunWorkerCompletedEventHandler
I tried
BackgroundEntityWorker.RunWorkerCompleted += BackgroundWorker_Completed;
but I lose my callback, how do I set it to have BackGroundWorker_Completed and maintain my callback?
BackgroundEntityWorker.RunWorkerCompleted += (sender, args) =>
{
If (onInitializeCallback != null)
{
onInitializeCallback();
}
}
but careful about BackgroundEntityWorker null access
public override void Initialize(System.Action onInitializeCallback = null)
{
If (BackgroundEntityWorker.RunWorkerCompleted != null)
{
BackgroundEntityWorker.RunWorkerCompleted();
}
}
You are redirecting twice the event. It would be easier for us, if you show how your complete event raising

Possible to get attribute of method in a second method call

[TestAttribute(Name = "Test")]
public void Test()
{
Test2();
}
public viod Test2()
{
Console.Write(TestAttribute.Name);
}
As shown above, is it possible to get the information of the attribute of Test when called in Test2?
Preferable without stacktrace.
Instead of using stacktrace you could use MethodBase.GetCurrentMethod() and pass it to your secondary method.
[TestAttribute(Name = "Test")]
public void Test()
{
Test2(MethodBase.GetCurrentMethod());
}
public viod Test2(MethodBase sender)
{
var attr = sender.GetCustomAttributes(typeof(TestAttribute), false).FirstOrDefault();
if(attr != null)
{
TestAttribute ta = attr as TestAttribute;
Console.WriteLine(ta.Name);
}
}
I wouldn't know how to get to the caller without stacktrace in your case:
[TestAttribute(Name = "Test")]
static void Test() {
Test2();
}
static void Test2() {
StackTrace st = new StackTrace(1);
var attributes = st.GetFrame(0).GetMethod().GetCustomAttributes(typeof(TestAttribute), false);
TestAttribute testAttribute = attributes[0] as TestAttribute;
if (testAttribute != null) {
Console.Write(testAttribute.Name);
}
}
An alternative is to explicitly pass the method information to the function:
[TestAttribute(Name = "Test")]
void TestMethod() {
MethodInfo thisMethod = GetType().GetMethod("TestMethod", BindingFlags.Instance | BindingFlags.NonPublic);
Test3(thisMethod);
}
static void Test3(MethodInfo caller) {
var attributes = caller.GetCustomAttributes(typeof(TestAttribute), false);
TestAttribute testAttribute = attributes[0] as TestAttribute;
if (testAttribute != null) {
Console.Write(testAttribute.Name);
}
}
By the way, this does not really look like something you want to do with reflection; I think that in this case the way to go is just this :)
void Test() {
Test2(name);
}
void Test2(string name) {
Console.Write(name);
}

Categories