Why use events for what I can do with Delegates?
Asked Answered
E

4

10

I know Events are always associated with Delegates. But, I am missing some core use of Events, and trying to understand that.

I created a simple Event program, as below, and it works perfectly fine.

namespace CompleteRef3._0
{
delegate void someEventDelegate();

class EventTester
{
    public event someEventDelegate someEvent;

    public void doEvent()
    {
        if (someEvent != null) someEvent();
    }

}

class Program
{
    static void EventHandler1()
    {
        Console.WriteLine("Event handler 1 called..");
    }

    static void EventHandler2()
    {
        Console.WriteLine("Event handler 2 called..");
    }
    static void EventHandler3()
    {
        Console.WriteLine("Event handler 3 called..");
    }


    static void Main(string[] args)
    {
        EventTester evt = new EventTester();
        evt.someEvent += EventHandler1;
        evt.someEvent += EventHandler2;
        evt.someEvent += EventHandler3;
        evt.doEvent();
        Console.ReadKey();

    }
}
}

I replaced the event declaration with delegates. That is I replaced the line public event someEventDelegate someEvent; with someEventDelegate someEvent; on the above program, and I still get the same result. Now, I was confused why we need to use Events, if it can be achieved by Delegates only. What is the real use of Events?

The modified program without events is as below -

namespace CompleteRef3._0
{
delegate void someEventDelegate();

class EventTester
{
    someEventDelegate someEvent;

    public void doEvent()
    {
        if (someEvent != null) someEvent();
    }

}

class Program
{
    static void EventHandler1()
    {
        Console.WriteLine("Event handler 1 called..");
    }

    static void EventHandler2()
    {
        Console.WriteLine("Event handler 2 called..");
    }
    static void EventHandler3()
    {
        Console.WriteLine("Event handler 3 called..");
    }


    static void Main(string[] args)
    {
        EventTester evt = new EventTester();
        evt.someEvent += EventHandler1;
        evt.someEvent += EventHandler2;
        evt.someEvent += EventHandler3;
        evt.doEvent();
        Console.ReadKey();

    }
}
}
Eyehole answered 3/3, 2013 at 5:37 Comment(1)
In short, exposing public delegate breaks encapsulation.Quality
C
10

Sure, you can use delegates because behind the scenes an event is a construct that wraps a delegate.

But the rationale of using events instead of delegates is the the same as for using properties instead of fields - data encapsulation. It's bad practice to expose fields (whatever they are - primitive fields or delegates) directly.

By the way, you missed a public keyword before your delegate field to make it possible in the second snippet.

Another "by the way" with the second snippet: for delegates you should use Delegate.Combine instead of "+=".

Consonantal answered 3/3, 2013 at 6:24 Comment(2)
oh ya.. my mistake I missed the public keyword.. thanks for catching it for me. I will edit it.. Good knowledge for me to use Delegate.Combine. I was not knowing it before!... Your response is good, exactly what I thought - data encapsulation.Eyehole
Good answer except for the last line. Why shouldn't we use + and += and so on with delegates? They were put into the language to make things pretty. It is still important to understand the difference between += with events (a syntax for calling the add event accessor), and += with variables (here x += y is much like x = x + y where + can be delegate combination, string concatenation, arithmetic addition of some type, or other overloads). One problem with Delegate.Combine is that types are not checked at all compile-time. With + the compile-time types are checked.Pretrice
H
22

Imagine you have 3 subscribers who are interested in your someEvent. Let's further imagine they are interested in receiving events from the same EventTester instance. For brevity, let's leave out the details of how the exact same instance is passed to all the clients. When I say clients, I mean any class who is a subscriber to the event.

Here is the instance:

EventTester evt = new EventTester();

They have subscribed to the event of the above instance as shown below:

Client 1

evt.someEvent += Client1Handler1;
evt.someEvent += Client1Handler2;

Client 2

evt.someEvent += Client2Handler1;

Client 3

evt.someEvent += Client3Handler1;
evt.someEvent += Client3Handler2;

Client 4:

Imagine client 4 did one of the 3 below:

// 1: See Note 1 below
evt.someEvent = null;

// 2: See Note 2 below
evt.someEvent = new someEventDelegate(MyHandler);

// 3: See Note 3 below
evt.someEvent();

//...
private void MyHandler()
{
    MessageBox.Show("Client 4");
}

Note 1

Client 1, 2, and 3 will not be getting any events anymore. Why? Because Client 4 just did this evt.someEvent = null; and in EventTester you have this line of code:

if (someEvent != null) someEvent();

Since that condition will not pass anymore, no event will be raised. There is nothing wrong with the above line of code by the way. But there is the problem with using delegates: It can be assigned to.

Note 2

It has been completely over-written to a brand new instance. Now regardless of the client, they will all see a message box that says "Client 4".

Note 3

Ooops all of a sudden one of the clients is broadcasting the event.


Imagine for a second EventTester was a Button and someEvent was Click. Imagine you had multiple clients interested in the click event of this button. All of a sudden, one client decides no-one else should get notifications (Note 1). Or one client decides that when the button is clicked, it will be handled only 1 way (Note 2). Or it has made the decision that it will decide when a button is clicked even though the button may not have been clicked (Note 3).


If you have an event and one of the clients tried the above, they will not be allowed and get a compile error, like this:

enter image description here

Hardaway answered 23/12, 2016 at 18:9 Comment(4)
Thank you. You have explained very well the use of Event. :)Discontinue
I am not sure if any other users have had this question or not but what if all the clients create separate instance of the main EventTester class. Then if client 4 does any of the scenarios you mentioned, the other clients will not be affected? Or if you could explain in which scenario all clients would use same instance of the EventTester classSustentacular
@dihren Correct, if separate instance then it would not matter. Here is a scenario: Imagine an application like Visual Studio wherein the user changes settings and many windows need to react and change accordingly. Imagine one of the programmers working on one of the windows did that by mistake? Now I am not saying VS is built that way, but there's an example. There are many scenarios, especially when building components wherein each component is its own thing but they all subscribe to something in a single parent window.Hardaway
@dihren In big enterprise applications, you cannot just go into an existing Load or Click event handler and add some code, that is frowned upon because changing existing code could potentially introduce bugs. So it needs to be done a different way, perhaps using the Open Closed Principle. When you have a single publisher and multiple subscribers, you run into the dangers detailed in the answer. Knowing this, you can come up with many scenarios yourself now :)Hardaway
C
10

Sure, you can use delegates because behind the scenes an event is a construct that wraps a delegate.

But the rationale of using events instead of delegates is the the same as for using properties instead of fields - data encapsulation. It's bad practice to expose fields (whatever they are - primitive fields or delegates) directly.

By the way, you missed a public keyword before your delegate field to make it possible in the second snippet.

Another "by the way" with the second snippet: for delegates you should use Delegate.Combine instead of "+=".

Consonantal answered 3/3, 2013 at 6:24 Comment(2)
oh ya.. my mistake I missed the public keyword.. thanks for catching it for me. I will edit it.. Good knowledge for me to use Delegate.Combine. I was not knowing it before!... Your response is good, exactly what I thought - data encapsulation.Eyehole
Good answer except for the last line. Why shouldn't we use + and += and so on with delegates? They were put into the language to make things pretty. It is still important to understand the difference between += with events (a syntax for calling the add event accessor), and += with variables (here x += y is much like x = x + y where + can be delegate combination, string concatenation, arithmetic addition of some type, or other overloads). One problem with Delegate.Combine is that types are not checked at all compile-time. With + the compile-time types are checked.Pretrice
C
9

The main purpose of events is to prevent subscribers from interfering with each other. If you do not use events, you can:

Replace other subscribers by reassigning delegate(instead of using the += operator), Clear all subscribers (by setting delegate to null), Broadcast to other subscribers by invoking the delegate.

Source: C# in a Nutshell

Cantonment answered 3/3, 2013 at 6:21 Comment(1)
+1. Assignment someEvent = new someEventDelegate(EventHandler2) is the problem you have with direct use of delegtes. Another link Learning C# 3.0: Master the fundamentals of C# 3.0 - Delegates and Events - this particular chapter is avialble on MSDN and goes into details on this topic.Chantilly
L
0
public class Program
{
    public static void Main()
    {
        Number myNumber = new Number(100000);
        myNumber.PrintMoney();
        myNumber.PrintNumber();
        Console.ReadKey();
    }
}

public class Number
{
    private PrintHelper _printHelper;

    public Number(int val)
    {
        _value = val;

        _printHelper = new PrintHelper();
        //subscribe to beforePrintEvent event
        _printHelper.beforePrintEvent += printHelper_beforePrintEvent;
    }
    //beforePrintevent handler
    void printHelper_beforePrintEvent(string message)
    {
        Console.WriteLine("BeforePrintEvent fires from {0}", message);
    }

    private int _value;

    public int Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public void PrintMoney()
    {
        _printHelper.PrintMoney(_value);
    }

    public void PrintNumber()
    {
        _printHelper.PrintNumber(_value);
    }

}
public class PrintHelper
{
    public delegate void BeforePrintDelegate(string message);
    public event BeforePrintDelegate beforePrintEvent;

    public PrintHelper()
    {

    }

    public void PrintNumber(int num)
    {
        if (beforePrintEvent != null)
            beforePrintEvent.Invoke("PrintNumber");

        Console.WriteLine("Number: {0,-12:N0}", num);

    }

    public void PrintDecimal(int dec)
    {
        if (beforePrintEvent != null)
            beforePrintEvent("PrintDecimal");

        Console.WriteLine("Decimal: {0:G}", dec);
    }

    public void PrintMoney(int money)
    {
        if (beforePrintEvent != null)
            beforePrintEvent("PrintMoney");

        Console.WriteLine("Money: {0:C}", money);
    }

    public void PrintTemperature(int num)
    {
        if (beforePrintEvent != null)
            beforePrintEvent("PrintTemerature");

        Console.WriteLine("Temperature: {0,4:N1} F", num);
    }
    public void PrintHexadecimal(int dec)
    {
        if (beforePrintEvent != null)
            beforePrintEvent("PrintHexadecimal");

        Console.WriteLine("Hexadecimal: {0:X}", dec);
    }
}
Leontineleontyne answered 19/10, 2019 at 14:9 Comment(1)
While this code may solve the question, including an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply.Bowel

© 2022 - 2024 — McMap. All rights reserved.