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 :)
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.
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:
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With