Skip to main content

C# Tutorial - Poking at Event Contents [Advanced]


Events in C# always feel like there is a little touch of black magic in the background keeping things running smoothly. We have had tutorials here before on events in C# - we took a look at how to create your own custom events in C# Snippet Tutorial - Custom EventHandlers, and we looked at the syntactic sugar behind the += and -= operators for events in C# Tutorial - EventAccessors. But we have never taken a look at what actually happens when you declare an event, and what happens when you invoke it.

The key behind events in C# in the MulticastDelegate. You've probably seen delegates before (if you haven't, they are just a reference to a method), and knowing that, you can probably wager a guess as to what a MulticastDelegate is. A MulticastDelegate is essentially a list of method references that acts like a regular old delegate in many ways. For instance, take a look at the following:
private void DoIt()
{
  var del = new Action(Method1);
  del();

  var del2 = Delegate.Combine(del, new Action(Method2)) as Action;
  del2();
}

private void Method1()
{ Console.WriteLine("I'm Method 1!"); }

private void Method2()
{ Console.WriteLine("I'm Method 2!"); }
 
If you run the function DoIt, the output is:
I'm Method 1!
I'm Method 1!
I'm Method 2!
 
This is because when del1 is invoked, it just calls Method1. But when del2 is invoked, both Method1 and Method2 get called, because the invocation list for del2 contains both methods. Both methods are in there because of the Combine call.

Once you know about the existence of the invocation list, you can start to do some interesting things. Take a look at the example below:
private void Method1()
{ Console.WriteLine("I'm Method 1!"); }

private void Method2()
{ Console.WriteLine("I'm Method 2!"); }

private void Method3()
{ Console.WriteLine("I'm Method 3!"); }

private void Method4()
{ Console.WriteLine("I'm Method 4!"); }

private void DoItAgain()
{ 
  var del = Delegate.Combine(new Action(Method1), new Action(Method2),
    new Action(Method3), new Action(Method4));

  var list = del.GetInvocationList();
  for (int i = 0; i < list.Length; i+=2)
  { ((Action)list[i])(); }
}
 
The output of the call to DoItAgain is the following:
I'm Method 1!
I'm Method 3!
 
In this case, we are skipping every other entry in the invocation list. Probably not a terribly useful thing to do, but you can see the power that being able to get to the list can give you.

Ok, so enough about the specifics of MulticastDelegates. How exactly are they used in events? Well, every time you write something like this in code:
public event EventHandler MyBestEventEver;
 
The compiler is going in behind you and placing down a MulticastDelegate in the background. Now, this only happens if you aren't declaring your own implementations of the add and remove accessors - when you do that (as we covered in C# Tutorial - EventAccessors), you are on your own for how the invocation list will actually be stored. But when you don't declare your own accessors, the compiler automatically uses a MulticastDelegate.

Now, when you are in the same class as where your event is declared, you can get to all the handy MulticastDelegate methods, like GetInvocationList:
public class MyEventTest
{
  public event EventHandler Changed;

  public void GetChangedHookCount()
  {
    var myList = Changed.GetInvocationList();
    Console.WriteLine(myList.Length);
  }
}
 
But if you are in a different class, the compiler says no:
public class MyEventTest
{
  public event EventHandler Changed;
}

public class MyOtherClass
{
  public void GetChangedHookCount()
  {
    MyEventTest test = new MyEventTest();
    var myList = test.Changed.GetInvocationList();
    Console.WriteLine(myList.Length);
  }
}

//Error: The event 'MyEventTest.Changed' can only appear on the left hand side 
//of += or -= (except when used from within the type 'MyEventTest')
 
This is because from outside MyEventTest the compiler can't make any guarantees about how that event is implemented inside MyEventTest - maybe something crazy was done with the accessors? Maybe there is no MulticastDelegate at all?

This is a real bummer - because it means that it is really hard to get to and/or modify the contents of an event from outside the class it was declared in. Now, granted, from a security and good practices point of view, this is a very good thing - but every once in a while, it would come in really handy.

But don't give up yet! If you know that the event is not using custom accessors (and most events don't), you can still use some reflection to get to these pieces:
public static class EventUtilities
{
  public static Delegate[] GetInvocationList(string eventName, object obj)
  {
    bool success;
    var result = TryGetInvocationList(eventName, obj, out success);
    if (success)
    { return result; }
    else
    { throw new InvalidOperationException(); }
  }

  public static Delegate[] TryGetInvocationList(string eventName, object obj,
      out bool success)
  {
    success = false;

    if (obj == null)
    { throw new ArgumentNullException("obj"); }

    if (eventName == null)
    { throw new ArgumentNullException("eventName"); }

    var field = GetField(eventName, obj.GetType());
    if (field == null)
    { return null; }

    success = true;
    var mDel = field.GetValue(obj) as MulticastDelegate;

    if (mDel == null)
    { return null; }
    else
    { return mDel.GetInvocationList(); }
  }

  public static bool ClearInvocationList(string eventName, object obj)
  {
    if (obj == null)
    { throw new ArgumentNullException("obj"); }

    if (eventName == null)
    { throw new ArgumentNullException("eventName"); }

    var field = GetField(eventName, obj.GetType());

    if (field == null)
    { return false; }

    field.SetValue(obj, null);
    return true;
  }

  private static FieldInfo GetField(string eventName, Type type)
  {
    var field = type.GetField(eventName, BindingFlags.Instance | 
      BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Public);

    if (field == null)
    { return null; }

    if (field.FieldType == typeof(MulticastDelegate))
    { return field; }

    if(field.FieldType.IsSubclassOf(typeof(MulticastDelegate)))
    { return field; }

    return null;
  }
}
 
So this crazy code is all about pulling that compiler created MulticastDelegate for an event out into the open where we can mess with it. Given the name of an event and the object that it resides on, we can pull the MulticastDelegate field off of the type using reflection (that is what the GetField method is doing) and then call GetValue to actually pull out the instance of the MulticastDelegate. For more information on how reflection works, you can check out these two tutorials.

An interesting thing to note is that the backing MulticastDelegate field is null when there is nothing hooked to the event - which means that clearing all hooks to an event is as simple as setting that field to null (which is what the ClearInvocationList is doing).

So how do we use these crazy methods? Its pretty simple - lets take the example above that wouldn't compile and fix it up:
public class MyEventTest
{
  public event EventHandler Changed;
}

public class MyOtherClass
{
  public void GetChangedHookCount()
  {
    MyEventTest test = new MyEventTest();
    var myList = EventUtilities.GetInvocationList("Changed", test);
    Console.WriteLine(myList.Length);
  }
}
 
In this case, this would print out 0, since nothing has been attached to the event.

One huge caveat to end this tutorial - these methods will not work for poking at the contents of events on pretty much any WPF element. This is because almost every event on a WPF element implements its own special add/remove accessors - they almost never use the standard MulticastDelegate backing. WPF elements use their own special internal EventHandlersStore, which while in the end still holds MulticastDelegates, is much harder to get to. If you need to get at the contents of a WPF event (and I wouldn't do this unless you really, really need to), I suggest pulling open Reflector to figure out exactly what to poke and prod at using reflection.

That's it for this tutorial on poking at events and MulticastDelegates. I hope it shed some light on what is a mysterious black box to many .NET developers. As always, drop any questions you might have below, and I'll do my best to answer them.

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...