Events (part 1, advanced)

Events link code dynamically together via subscriptions. There are many ways to achieve this. To better understand events, we will start with the old school approach. It is pretty much Java style. You would not do such in C#.

public interface IListener {
    void OnEvent(int xDummy);
} // interface

public class AnyClass {
    private List<Ilistener> _Listeners = new List<Ilistener>();

    public void AddListener(IListener xListener) {
        lock (_Listeners) { _Listeners.Add(xListener); }
    } //

    public void RemoveListener(IListener xListener) {
        lock (_Listeners) { _Listeners.Remove(xListener); }
    } //

    protected void RaiseEvent() {
        lock (_Listeners) {
            foreach (IListener lListener in _Listeners) lListener.OnEvent(0);
        }
    }//

    public void DoSomeCalc() {
        Console.WriteLine("calculating something");
        Console.WriteLine("done");
        Console.WriteLine("going to tell others");
        RaiseEvent();
    } //
} // class

public class MyLittleProgram : IListener {
    public void StartDemo() {
        AnyClass c = new AnyClass();
        c.AddListener(this);
        c.DoSomeCalc();
        c.RemoveListener(this);
    } //

    public void OnEvent(int xDummy) {
        Console.WriteLine("Hey, cool! I got a notification");
    } //
} // class

static void Main(string[] args) {    
    MyLittleProgram p = new MyLittleProgram();
    p.StartDemo();

    Console.ReadLine();
} //

Above example program uses a List that stores classes. An event is raised by iterating through that list and calling the desired method. This is a lot of code for something really simple.
In the post “Delegates (basics)” https://csharphardcoreprogramming.wordpress.com/2013/12/16/delegates-basics/ we have learned to use delegates. And let’s emphasize it again: We are talking about MulticastDelegates. In the post “Lambda expressions (advanced)” https://csharphardcoreprogramming.wordpress.com/2013/12/17/lambda-expressions-advanced/ we then came across Func and Action. The next step would be to make use of Action.

public class MyActionEvent {
    public Action OnChange { get; set; }

    public void RaiseEvent() {
        Action lAction = OnChange;
        if (lAction == null) return;
        lAction();
    }//
} // class

static void Main(string[] args) {
    MyActionEvent lEventClass = new MyActionEvent();
    lEventClass.OnChange += () => Console.WriteLine("I got a notification.");
    lEventClass.OnChange += () => Console.WriteLine("Me too.");
    lEventClass.RaiseEvent();

    Console.ReadLine();
} //

Notice that we used a local delegate variable (Action lAction = OnChange;) and did not call OnEvent directly. This is important in a multithreaded environment. Allthough it is unlikely, the event could still turn null before it gets raised.

Above code is much more legible now. But it is really bad practice. The event is accessible from outside. The class MyActionEvent loses control over the event. The Action OnChange is accessible and can be raised from outside the class. This is why C# implemented the “event” keyword. Code no longer uses public properties but public event fields, which are properly protected.
An event can only be assigned by using “+=”, whereas an Action can also be entirely overridden with “=”. It makes programming a little bit safer, you don’t run the risk of removing all previous subscriptions by mistake. Also events can only be raised by code within the same class. There is no way events could be raised from outside.

To make your life easier you can assign an empty delegate during the initialization process. By using “= delegate { };” there is no further need to check for the null condition, especially that no outside code can assign null to the event. Only code inside the class can assign null. Therefore the class is in full control of the event.

public class MyActionEvent2 {
    public event Action OnChange = delegate { };

    public void RaiseEvent() {
        OnChange();
        Console.WriteLine("number of event handlers: " + OnChange.GetInvocationList().Length);
    }//
} // class

Personally I do not like the approach with empty delegates. The number of delegates is artificially increased by one. A null check is pretty much using no time at all, so why should someone prefer to call an empty method each time an event is raised?

The next step is to replace the dirty Action by a proper delegate definition, which is EventHandler<>. By default its parameters are the sender object and some event arguments. This is a C# convention and should be followed. Surely you are familiar with such event calls from the Winforms/WPF environment.

public class MyArgs : EventArgs {
    public MyArgs(int xValue) {
        Value = xValue;
    } // constructor

    public int Value { get; set; }
} // class

public class MyActionEvent3 {
    public event EventHandler<myargs> OnChange;

    public void RaiseEvent() {
        EventHandler<myargs> lHandler = OnChange;
        if (lHandler == null) return;
        lHandler(this, new MyArgs(0));                
    }//
} // class

static void Main(string[] args) {
    MyActionEvent3 lEventClass = new MyActionEvent3();
    lEventClass.OnChange += (sender, e) => Console.WriteLine("Event raised, field value is: " + e.Value);            
    lEventClass.RaiseEvent();
    
    Console.ReadLine();
} //
Advertisements

About Bastian M.K. Ohta

Happiness only real when shared.

Posted on December 18, 2013, in Advanced, C#, Delegates, Threading and tagged , , , , , , , , , , , . Bookmark the permalink. 1 Comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: