What is the quickest way to detect missing event unsubscribes? - c#

We have an application that is well beyond a quick design improvement and is in a state where everything works, but there is a memory leak. We cannot use any of the popular profilers out there because we're sandboxed inside the Unity3D engine in some old version of Mono.
We have many transient objects with many events at play, and we suspect some poor design and missing event unsubscriptions could be at play here.
I'd love to know if anyone can think of some simple ways to scan the project for missing event unsubscribes. I'm hoping it is possible to use Visual Studio itself to write a script that can look at an event and tell us if the -= and += are in equal existence in the project.

Counting -= and += in code is very error-prone, because event-related memory leaks are most often caused by some conditional execution.
If you're short of profilers, you could use Reflection to enumerate any object's field-like event subscribers at any given time using something like this:
public static class InvocationHelper
{
public static Dictionary<string,Delegate[]> GetInvocationList<TClass>(TClass source)
{
if (null == source)
return new Dictionary<string, Delegate[]>();
var retval = new Dictionary<string,Delegate[]>();
var members = typeof (TClass).GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).
Where(t=>t.MemberType == MemberTypes.Event).ToArray();
foreach (var memberInfo in members)
{
var field = typeof (TClass).GetField(memberInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null)
continue;
var delegateField = field.GetValue(source) as Delegate;
if(null == delegateField)
continue;
retval[memberInfo.Name] = delegateField.GetInvocationList();
}
return retval;
}
}
Method above walks through all events in source and returns dictionary with invocation lists for events encountered.
This works only for field-like events (and for custom events you can count subscribers manually).
For example, suppose you have some class with field-like event SomeEvent:
public class SomeEventHolder
{
public delegate void SomeEventHandler(bool par);
public event SomeEventHandler SomeEvent;
protected virtual void OnSomeEvent(bool par)
{
var handler = SomeEvent;
if (handler != null) handler(par);
}
}
You create instance of this class and subscribe three times to SomeEvent but unsubscribe only once:
var instance = new SomeEventHolder();
//....
instance.SomeEvent += inst_SomeEvent;
instance.SomeEvent += inst_SomeOther;
instance.SomeEvent += inst_SomeEvent;
instance.SomeEvent -= inst_SomeEvent;
Then, you can call GetInvocationList for instance and get subscribers left, and, say, output their list to console:
var invocationList = InvocationHelper.GetInvocationList(instance);
foreach (var item in invocationList)
{
Console.WriteLine("Subscribers for {0} : {1}",
item.Key,
string.Join(Environment.NewLine, item.Value.Select(s => s.Method.ToString())));
}
This would result in
Subscribers for SomeEvent : Void inst_SomeEvent(Boolean)
Void inst_SomeOther(Boolean)

Related

C# copy one control event to another programmatically [duplicate]

I want do something like this:
Button btn1 = new Button();
btn1.Click += new EventHandler(btn1_Click);
Button btn2 = new Button();
// Take whatever event got assigned to btn1 and assign it to btn2.
btn2.Click += btn1.Click; // The compiler says no...
Where btn1_Click is already defined in the class:
void btn1_Click(object sender, EventArgs e)
{
//
}
This won't compile, of course ("The event 'System.Windows.Forms.Control.Click' can only appear on the left hand side of += or -="). Is there a way to take the event handler from one control and assign it to another at runtime? If that's not possible, is duplicating the event handler and assigning it to another control at runtime doable?
A couple of points: I have googled the heck out of this one for awhile and found no way of doing it yet. Most of the attempted approaches involve reflection, so if you read my question and think the answer is incredibly obvious, please try to compile the code in Visual Studio first. Or if the answer really is incredibly obvious, please feel free to slap me with it. Thanks, I'm really looking forward to seeing if this is possible.
I know I could just do this:
btn2.Click += new EventHandler(btn1_Click);
That's not what I'm looking for here.
This is also not what I'm looking for:
EventHandler handy = new EventHandler(btn1_Click);
Button btn1 = new Button();
btn1.Click += handy;
Button btn2 = new Button();
btn2.Click += handy;
Yeah, it's technically possible. Reflection is required because many of the members are private and internal. Start a new Windows Forms project and add two buttons. Then:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
button1.Click += new EventHandler(button1_Click);
// Get secret click event key
FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
object secret = eventClick.GetValue(null);
// Retrieve the click event
PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList events = (EventHandlerList)eventsProp.GetValue(button1, null);
Delegate click = events[secret];
// Remove it from button1, add it to button2
events.RemoveHandler(secret, click);
events = (EventHandlerList)eventsProp.GetValue(button2, null);
events.AddHandler(secret, click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Yada");
}
}
}
If this convinces you that Microsoft tried really hard to prevent your from doing this, you understood the code.
No, you can't do this. The reason is encapsulation - events are just subscribe/unsubscribe, i.e. they don't let you "peek inside" to see what handlers are already subscribed.
What you could do is derive from Button, and create a public method which calls OnClick. Then you just need to make btn1 an instance of that class, and subscribe a handler to btn2 which calls btn1.RaiseClickEvent() or whatever you call the method.
I'm not sure I'd really recommend it though. What are you actually trying to do? What's the bigger picture?
EDIT: I see you've accepted the version which fetches the current set of events with reflection, but in case you're interested in the alternative which calls the OnXXX handler in the original control, I've got a sample here. I originally copied all events, but that leads to some very odd effects indeed. Note that this version means that if anyone subscribes to an event in the original button after calling CopyEvents, it's still "hooked up" - i.e. it doesn't really matter when you associate the two.
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
class Test
{
static void Main()
{
TextBox output = new TextBox
{
Multiline = true,
Height = 350,
Width = 200,
Location = new Point (5, 15)
};
Button original = new Button
{
Text = "Original",
Location = new Point (210, 15)
};
original.Click += Log(output, "Click!");
original.MouseEnter += Log(output, "MouseEnter");
original.MouseLeave += Log(output, "MouseLeave");
Button copyCat = new Button
{
Text = "CopyCat",
Location = new Point (210, 50)
};
CopyEvents(original, copyCat, "Click", "MouseEnter", "MouseLeave");
Form form = new Form
{
Width = 400,
Height = 420,
Controls = { output, original, copyCat }
};
Application.Run(form);
}
private static void CopyEvents(object source, object target, params string[] events)
{
Type sourceType = source.GetType();
Type targetType = target.GetType();
MethodInfo invoker = typeof(MethodAndSource).GetMethod("Invoke");
foreach (String eventName in events)
{
EventInfo sourceEvent = sourceType.GetEvent(eventName);
if (sourceEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", sourceType.Name, eventName);
continue;
}
// Note: we currently assume that all events are compatible with
// EventHandler. This method could do with more error checks...
MethodInfo raiseMethod = sourceType.GetMethod("On"+sourceEvent.Name,
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
if (raiseMethod == null)
{
Console.WriteLine("Can't find {0}.On{1}", sourceType.Name, sourceEvent.Name);
continue;
}
EventInfo targetEvent = targetType.GetEvent(sourceEvent.Name);
if (targetEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", targetType.Name, sourceEvent.Name);
continue;
}
MethodAndSource methodAndSource = new MethodAndSource(raiseMethod, source);
Delegate handler = Delegate.CreateDelegate(sourceEvent.EventHandlerType,
methodAndSource,
invoker);
targetEvent.AddEventHandler(target, handler);
}
}
private static EventHandler Log(TextBox output, string text)
{
return (sender, args) => output.Text += text + "\r\n";
}
private class MethodAndSource
{
private readonly MethodInfo method;
private readonly object source;
internal MethodAndSource(MethodInfo method, object source)
{
this.method = method;
this.source = source;
}
public void Invoke(object sender, EventArgs args)
{
method.Invoke(source, new object[] { args });
}
}
}
I did some digging around with #nobugz's solution and came up with this generic version which could be used on most general-purpose objects.
What I found out is that events for, dare I say, automatic events actually are compiled with a backing delegate field of the same name:
So here's one for stealing event handlers for simpler objects:
class Program
{
static void Main(string[] args)
{
var d = new Dummy();
var d2 = new Dummy();
// Use anonymous methods without saving any references
d.MyEvents += (sender, e) => { Console.WriteLine("One!"); };
d.MyEvents += (sender, e) => { Console.WriteLine("Two!"); };
// Find the backing field and get its value
var theType = d.GetType();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingField = theType.GetField("MyEvents", bindingFlags);
var backingDelegate = backingField.GetValue(d) as Delegate;
var handlers = backingDelegate.GetInvocationList();
// Bind the handlers to the second instance
foreach (var handler in handlers)
d2.MyEvents += handler as EventHandler;
// See if the handlers are fired
d2.DoRaiseEvent();
Console.ReadKey();
}
}
class Dummy
{
public event EventHandler MyEvents;
public void DoRaiseEvent() { MyEvents(this, new EventArgs()); }
}
Thought it might be useful to some.
But do note that the way events are wired in Windows Forms components is rather different. They are optimized so that multiple events doesn't take up a lot of memory just holding nulls. So it'll need a little more digging around, but #nobugz has already done that :-)
The article Delegates and events about combined delegates might help clarify a lot of points in answers.
You could use a common event handler for your buttons and your picture boxes (as per the comments on an earlier answer) and then use the 'sender' object to determine how to handle the event at runtime.

Event Handler With Custom Parameters Not Removing

I am adding a custom event to a DataGridView to handle cell clicks. This
needs a parameter passed to it.
I first attempt to delete the previous event handler, as I am sending the same DataGridView in and populating it each time.
//To delete the old handle
DataGridViewToPopulate.CellClick -= (sender, e) => DataGridView_CellClick(sender, e, SourceObject);
//To add new handle
DataGridViewToPopulate.CellClick += (sender, e) => DataGridView_CellClick(sender, e, SourceObject);
My problem is, when this runs the second time, and the SourceObject has changed, the event still has the original SourceObject being sent to it (I believe the original handle is never removed).
I need to dynamically remove all CellClick events (or even all, there aren't that many).
Maybe "this", when you are calling the second time the -= operator, is a different object than the first time you executed the +=. So that -= will not detach that first object, and you would be attaching a second object while not removing the first one.
If that's the case, you should try to find the way to detach that first object.
I was able to find a solution with reflection,
private static void UnsubscribeOne(object target)
{
Delegate[] subscribers;
Type targetType;
string EventName = "EVENT_DATAGRIDVIEWCELLCLICK";
targetType = target.GetType();
do
{
FieldInfo[] fields = targetType.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
foreach (FieldInfo field in fields)
{
if (field.Name == EventName)
{
EventHandlerList eventHandlers = ((EventHandlerList)(target.GetType().GetProperty("Events", (BindingFlags.FlattenHierarchy | (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(target, null)));
Delegate d = eventHandlers[field.GetValue(target)];
if ((!(d == null)))
{
subscribers = d.GetInvocationList();
foreach (Delegate d1 in subscribers)
{
targetType.GetEvent("CellClick").RemoveEventHandler(target, d1);
}
return;
}
}
}
targetType = targetType.BaseType;
} while (targetType != null);
}

Is it possible to check whether an Event is null if i only got the EventDescriptor?

My constructor parses its instance for events with a certain attribute:
_EventAttributes = from EventDescriptor a in TypeDescriptor.GetEvents(this)
let attribute = a.Attributes[typeof(MyAttribute)] as MyAttribute
where attribute != null
select new EventAttributeTuple { Event = a, Attribute = attribute };
Later in the code i want to check whether the corresponding event is null (nobody is interested in the event) to decide whether or not i have to write it in my XML output:
//write events
foreach (var attr in _EventAttributes)
{
if (check corresponding attr.Event object is null)
{
writer.WriteAttributeString(attr.Attribute.Name, GetRPCAdress(attr.Event.Name));
}
}
EDIT:
Getting the EventInfo is also quite easy:
var evtInfo = this.GetType().GetEvent(attr.Event.Name);
But still, i don't know how to check whether the event has subscribers or not.
EDIT2:
After looking at the generated code with dotPeek, I think there is no chance to access any field here:
//original code
[MyAttribute("onclick")]
public event EventHandler<MouseArg> Click;
//generated code
[MyAttribute("onclick")]
public event EventHandler<MouseArg> Click
{
add
{
EventHandler<MouseArg> eventHandler = this.Click;
EventHandler<MouseArg> comparand;
do
{
comparand = eventHandler;
eventHandler = Interlocked.CompareExchange<EventHandler<MouseArg>>(ref this.Click, comparand + value, comparand);
}
while (eventHandler != comparand);
}
remove
{
EventHandler<MouseArg> eventHandler = this.Click;
EventHandler<MouseArg> comparand;
do
{
comparand = eventHandler;
eventHandler = Interlocked.CompareExchange<EventHandler<MouseArg>>(ref this.Click, comparand - value, comparand);
}
while (eventHandler != comparand);
}
}
There may be a chance, if i handle all of the event add/remove operations myself, but that seems very tedious. Anyone has a better idea?
EDIT3:
I got it, i was using this.GetType() which does not give me the declaring type but was a subclass of the class which declares the event, hence i was not able to retrieve the field.
It now works like this:
var evtValue = attr.Event.ComponentType.GetField(attr.Event.Name,
BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this);
Sorry, this is impossible in general case, for instance:
class CounterExample {
public event EventHandler MyEvent {
add {
// You can't control me here: whether I truly add an event or not
// and even if I always add the event - where I store it
}
remove {
// You can't control me here: whether I truly remove an event or not
// and even if I always remove the event - from where I delete it
}
}
}
But in many cases you can do a hack (be careful though):
class SimpleCase {
// Usual event realization (default add and remove accessors)
public event EventHandler MyEvent;
}
...
SimpleCase cs = new SimpleCase();
cs.MyEvent += ...
...
// Since MyEvent has default accessors, lets check the default storage:
FieldInfo fi = cs.GetType().GetField("MyEvent", BindingFlags.NonPublic | BindingFlags.Instance);
Boolean myEventAssigned = !Object.ReferenceEquals(fi.GetValue(cs), null);

How do I unsubscribe all handlers from an event for a particular class in C#?

Basic premise:
I have a Room which publishes an event when an Avatar "enters" to all Avatars within the Room. When an Avatar leaves the Room I want it to remove all subscriptions for that room.
How can I best unsubscribe the Avatar from all events in the room before I add the Avatar to a new Room and subscribe to the new Room's events?
The code goes something like this:
class Room
{
public event EventHandler<EnterRoomEventArgs> AvatarEntersRoom;
public event EvnetHandler<LeaveRoomEventArgs> AvatarLeavesRoom;
public event EventHandler<AnotherOfManyEventArgs> AnotherOfManayAvatarEvents;
public void AddPlayer(Avatar theAvatar)
{
AvatarEntersRoom(this, new EnterRoomEventArgs());
AvatarEntersRoom += new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
AvatarLeavesRoom += new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
AnotherOfManayAvatarEvents += new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
}
}
class Avatar
{
public void HandleAvatarEntersRoom(object sender, EnterRoomEventArgs e)
{
Log.Write("avatar has entered the room");
}
public void HandleAvatarLeaveRoom(object sender, LeaveRoomEventArgs e)
{
Log.Write("avatar has left room");
}
public void HandleAnotherOfManayAvatarEvents(object sender, AnotherOfManyEventArgs e)
{
Log.Write("another avatar event has occurred");
}
}
Each delegate has a method named GetInvocationList() that returns all the actual delegates that have been registered. So, assuming the delegate Type (or event) is named say MyDelegate, and the handler instance variable is named myDlgHandler, you can write:
Delegate[] clientList = myDlgHandler.GetInvocationList();
foreach (var d in clientList)
myDlgHandler -= (d as MyDelegate);
to cover the case where it might be null,
if(myDlgHandler != null)
foreach (var d in myDlgHandler.GetInvocationList())
myDlgHandler -= (d as MyDelegate);
Probably the simplest way to accomplish this would be to store all of your subscribed events for an avatar in an ArrayList of delegates to the events.
When the avatar leaves the room, simply loop through the list of delegates performing a standard remove (-=).
Is there anything wrong with a standard remove?
public void RemovePlayer(Avatar theAvatar) {
AvatarEntersRoom -= new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
}
EDIT
Based on your update it appears that you want code that will remove a particular object from all events on a particular class. There is no realistic way to accomplish this goal. It's often a bit verbose but the best way is to individually add/remove a particular object method combo from every event.
The only way to get close to this functionality is to use reflection. You could reflectively grab all events on your class and then do some magic to find all instances of a class within the event chain. This will only be a partial solution though because it will ignore such things as a lambda expression event handlers.
you can run on all the event subscribers with:
_Event.GetInvocationList()
and remove each event handler.
Delegate[] subscribers = myEvent.GetInvocationList();
for(int i = 0; i < subscribers.Length; i++)
{
myEvent -= subscribers[i] as yourDelegateType;
}
What I'd like to do is in debug (do not think that this is good performance wise for release and one should catch it during development) throw exceptions when a class's events are not unsubscribed, this is the method that I use:
#if DEBUG
private void CheckEventHasNoSubscribers(Delegate eventDelegate)
{
if (eventDelegate != null)
if (eventDelegate.GetInvocationList().Length != 0)
{
var subscriberCount = eventDelegate.GetInvocationList().Length;
// determine the consumers of this event
var subscribers = new StringBuilder();
foreach (var del in eventDelegate.GetInvocationList())
subscribers.AppendLine((subscribers.Length != 0 ? ", " : "") + del.Target);
// throw an exception listing all current subscription that would hinder GC on them!
throw new Exception(
$"Event:{eventDelegate.Method.Name} still has {subscriberCount} subscribers, with the following targets [{subscribers}]");
}
}
#endif
The in my Dispose of the item that owns the delegate, or any other location where you're workflow supposed to release the object I would call it like this.
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (_orderCacheLock != null)
_orderCacheLock.Dispose();
if(_SettingTradeTimeOut!=null)
_SettingTradeTimeOut.Dispose();
_orderCacheLock = null;
#if DEBUG
CheckEventHasNoSubscribers(OnIsProfitable);
CheckEventHasNoSubscribers(OnPropertyChanged);
#endif
disposedValue = true;
}
}
It's then super easy to find the subscribers to these "orphaned" events and fix the code
ps:
An Extension of this "practice pattern" looks like this.
public static void CheckEventHasNoSubscribers(this Delegate eventDelegate)
{
if (eventDelegate != null)
if (eventDelegate.GetInvocationList().Length != 0)
{
var subscriberCount = eventDelegate.GetInvocationList().Length;
// determine the consumers of this event
var subscribers = new StringBuilder();
foreach (var del in eventDelegate.GetInvocationList())
subscribers.AppendLine((subscribers.Length != 0 ? ", " : "") + del.Target);
// point to the missing un-subscribed events
throw new Exception( $"Event:{eventDelegate.Method.Name} still has {subscriberCount} subscribers, with the following targets [{subscribers}]");
}
}

How do I unregister all handlers for a form event?

I have 2 handlers using the same form. How do I remove the handlers before adding the new one (C#)?
If you are working in the form itself, you should be able to do something like:
PseudoCode:
Delegate[] events = Form1.SomeEvent.GetInvokationList();
foreach (Delegate d in events)
{
Form1.SomeEvent -= d;
}
From outside of the form, your SOL.
If you know what those handlers are, just remove them in the same way that you subscribed to them, except with -= instead of +=.
If you don't know what the handlers are, you can't remove them - the idea being that the event encapsulation prevents one interested party from clobbering the interests of another class in observing an event.
EDIT: I've been assuming that you're talking about an event implemented by a different class, e.g. a control. If your class "owns" the event, then just set the relevant variable to null.
I realize this question is rather old, but hopefully it will help someone out. You can unregister all the event handlers for any class with a little reflection.
public static void UnregisterAllEvents(object objectWithEvents)
{
Type theType = objectWithEvents.GetType();
//Even though the events are public, the FieldInfo associated with them is private
foreach (System.Reflection.FieldInfo field in theType.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance))
{
//eventInfo will be null if this is a normal field and not an event.
System.Reflection.EventInfo eventInfo = theType.GetEvent(field.Name);
if (eventInfo != null)
{
MulticastDelegate multicastDelegate = field.GetValue(objectWithEvents) as MulticastDelegate;
if (multicastDelegate != null)
{
foreach (Delegate _delegate in multicastDelegate.GetInvocationList())
{
eventInfo.RemoveEventHandler(objectWithEvents, _delegate);
}
}
}
}
}

Categories

Resources