Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do C# Events work behind the scenes?

I'm using C#, .NET 3.5. I understand how to utilize events, how to declare them in my class, how to hook them from somewhere else, etc. A contrived example:

public class MyList {     private List<string> m_Strings = new List<string>();     public EventHandler<EventArgs> ElementAddedEvent;      public void Add(string value)     {         m_Strings.Add(value);         if (ElementAddedEvent != null)             ElementAddedEvent(value, EventArgs.Empty);     } }  [TestClass] public class TestMyList {     private bool m_Fired = false;      [TestMethod]     public void TestEvents()     {         MyList tmp = new MyList();         tmp.ElementAddedEvent += new EventHandler<EventArgs>(Fired);         tmp.Add("test");         Assert.IsTrue(m_Fired);     }      private void Fired(object sender, EventArgs args)     {         m_Fired = true;     } } 

However, what I do not understand, is when one declares an event handler

public EventHandler<EventArgs> ElementAddedEvent; 

It's never initialized - so what, exactly, is ElementAddedEvent? What does it point to? The following won't work, because the EventHandler is never initialized:

[TestClass] public class TestMyList {     private bool m_Fired = false;      [TestMethod]     public void TestEvents()     {         EventHandler<EventArgs> somethingHappend;         somethingHappend += new EventHandler<EventArgs>(Fired);         somethingHappend(this, EventArgs.Empty);         Assert.IsTrue(m_Fired);     }      private void Fired(object sender, EventArgs args)     {         m_Fired = true;     } } 

I notice that there is an EventHandler.CreateDelegate(...), but all the method signatures suggest this is only used for attaching Delegates to an already existing EventHandler through the typical ElementAddedEvent += new EventHandler(MyMethod).

I'm not sure if what I am trying to do will help... but ultimately I'd like to come up with an abstract parent DataContext in LINQ whose children can register which table Types they want "observed" so I can have events such as BeforeUpdate and AfterUpdate, but specific to types. Something like this:

public class BaseDataContext : DataContext {     private static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> m_ObservedTypes = new Dictionary<Type, Dictionary<ChangeAction, EventHandler>>();      public static void Observe(Type type)     {         if (m_ObservedTypes.ContainsKey(type) == false)         {             m_ObservedTypes.Add(type, new Dictionary<ChangeAction, EventHandler>());              EventHandler eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;             m_ObservedTypes[type].Add(ChangeAction.Insert, eventHandler);              eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;             m_ObservedTypes[type].Add(ChangeAction.Update, eventHandler);              eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;             m_ObservedTypes[type].Add(ChangeAction.Delete, eventHandler);         }     }      public static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> Events     {         get { return m_ObservedTypes; }     } }   public class MyClass {     public MyClass()     {         BaseDataContext.Events[typeof(User)][ChangeAction.Update] += new EventHandler(OnUserUpdate);     }      public void OnUserUpdated(object sender, EventArgs args)     {         // do something     } } 

Thinking about this made me realize I don't really understand what's happening under the hod with events - and I would like to understand :)

like image 208
Matt Avatar asked Oct 17 '08 20:10

Matt


People also ask

How does C coding work?

C is what's referred to as a compiled language, meaning you have to use a compiler to turn the code into an executable file before you can run it. The code is written into one or more text files, which you can open, read and edit in any text editor, such as Notepad in Windows, TextEdit on a Mac, and gedit in Linux.


1 Answers

I've written this up in a fair amount of detail in an article, but here's the summary, assuming you're reasonably happy with delegates themselves:

  • An event is just an "add" method and a "remove" method, in the same way that a property is really just a "get" method and a "set" method. (In fact, the CLI allows a "raise/fire" method as well, but C# never generates this.) Metadata describes the event with references to the methods.
  • When you declare a field-like event (like your ElementAddedEvent) the compiler generates the methods and a private field (of the same type as the delegate). Within the class, when you refer to ElementAddedEvent you're referring to the field. Outside the class, you're referring to the field.
  • When anyone subscribes to an event (with the += operator) that calls the add method. When they unsubscribe (with the -= operator) that calls the remove.
  • For field-like events, there's some synchronization but otherwise the add/remove just call Delegate.Combine/Remove to change the value of the auto-generated field. Both of these operations assign to the backing field - remember that delegates are immutable. In other words, the autogenerated code is very much like this:

    // Backing field // The underscores just make it simpler to see what's going on here. // In the rest of your source code for this class, if you refer to // ElementAddedEvent, you're really referring to this field. private EventHandler<EventArgs> __ElementAddedEvent;  // Actual event public EventHandler<EventArgs> ElementAddedEvent {     add     {         lock(this)         {             // Equivalent to __ElementAddedEvent += value;             __ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);         }     }     remove     {         lock(this)         {             // Equivalent to __ElementAddedEvent -= value;             __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);         }     } } 
  • The initial value of the generated field in your case is null - and it will always become null again if all subscribers are removed, as that is the behaviour of Delegate.Remove.

  • If you want a "no-op" handler to subscribe to your event, so as to avoid the nullity check, you can do:

    public EventHandler<EventArgs> ElementAddedEvent = delegate {}; 

    The delegate {} is just an anonymous method which doesn't care about its parameters and does nothing.

If there's anything that's still unclear, please ask and I'll try to help!

like image 166
Jon Skeet Avatar answered Sep 27 '22 21:09

Jon Skeet