Background
This is the third article out of four where I continue to discuss delegates and introduce events.
Because the articles are logically and contextually connected, I would recommend reading them accordingly.
In my first article, I discussed delegates and the way C# compiler treats them. We already know that a delegate is an object of a specific type. It is created by the compiler, it has references to one or many functions; when it calls the Invoke
method, it actually calls the referenced function(s).
This is the link to Delegates in C# - Attempt to Look Inside - Part 1.
The second article explains how a delegate can use multithreading. The delegate can use the Invoke
method to call the referenced function(s) synchronously or BeginInvoke
/EndInvoke
methods to make an asynchronous call.
This is the link to Delegates in C# - Attempt to Look Inside - Part 2.
More about Delegates: A Different Approach
After I published the first two articles, I received some positive feedback. Many programmers liked the approach of explaining programming terms not only using definitions and code examples but also making up real life situations and to try to translate them into programming. When you create strong associations between real life and programming, it helps you program better. Your program comes to life; you can see where and how to use programming techniques that you've never used before.
Let’s take delegates as an example.
We use delegates in everyday life without even knowing it. Every now and then we order food by phone, buy things online, take online courses; talk with friends via email, etc... Look:
- Order food by the phone: It is a delegate representing the restaurant preparing food for you.
- Buy things online: The website is a delegate representing a seller.
- Take online course: The website is a delegate representing a college.
- Talk with a friend via email: Email is a delegate representing your friend.
Never thought about it? But it's true. A delegate is not only a person representing somebody at a conference or a meeting but also anything that can represent anything.
The notion of phones, websites, etc... basically is a delegates declaration in programming world. You globally declare the phone delegate, website delegate, or email delegate. They can be accessed by everyone but they are not the real thing.
To make them real, you have to instantiate them. In the real world, you cannot just call using a phone, you call using a specific phone number.
So when you get a number for your call, you create an instance of the phone delegate.
This number belongs to something (a restaurant for example). This is a method or function in the programming world. So when you actually call the number, you invoke the delegate.
And we can pass the delegates. You can pass a phone number of a restaurant to a friend. Now your friend can use this delegate.
But there is more in this.
Using a phone, you can order not only food but also talk to people, order tickets, make an appointment, etc…
So, one delegate – a phone – can represent many different things. It depends on the phone number and the message that you pass via the phone.
We don’t need to have four different phones in our apartment to call for four different things. One phone is sufficient.
Do you get the idea?
The same delegate can be used for many different functions/methods. Just the signature should be observed.
And how does this apply to programming? Imagine that you have a procedure that logs error messages into a file. It returns void
and accepts a string
parameter: error message.
Another procedure logs error messages into a database. It returns void
and accepts a string
parameter: error message.
Another procedure emails error messages to an admin. It returns void
and accepts a string
parameter: error message.
You can declare a public
delegate representing any of these procedures. It must have the procedures’ signature: it returns void
and accepts a string
parameter.
In the program, you create an instance of this delegate pointing to a procedure that is more appropriate. When you use the delegate’s instance, it invokes the proper procedure and logs an error message the way you want it.
Events
Using this approach, I want to take a close look at events. There is no doubt that a C# programmer uses events a lot. I cannot imagine any production program without events. That means that everyone knows how to use events and this creates a challenge for me to bring something knew into the topic. Nevertheless, I will try to find another angle in my explanation.
What is an event?
According to the Google dictionary, an event is a thing that happens, esp. one of importance.
If we want to single out something really important to us, we would make an event for it.
In order to bring the association closer to programming, let us assume that we don’t know when the event occurs.
Example: A family is waiting for a baby. The exact delivery date is naturally unknown.
They want family and friends to visit the future mother in the hospital. So they announce the event. They publish a message on Facebook asking potential participants to let them know if they are coming.
If I am a friend and want to come, I would call them and give my phone number asking them to notify me when it happens.
When the event occurs, they call and notify me and I will come and visit the family in the hospital.
Do you want to plunge deeper into this? Follow me then.
When the family announced the event, they actually brought a delegate in place. It is not instantiated yet.
If it was instantiated, it would be used to notify all participants.
The first caller creates an instance of this delegate.
A special function must be created and the reference to this function is placed into the delegate.
When an event occurs, they run the event’s Invoke
method (if the event is not null
: they have participants). Every participant will be notified by running the function created for the event.
In programming terms, the family published an event and participants subscribed for the event.
Important Disclosure
An event is a delegate. Everything that we know about delegates, you can apply to an event. By publishing an event, you select a proper delegate type from previously declared delegates or from pre-constructed by the environment delegates.
Event Modifies the Delegate
At the same time, an event modifies this delegate so we can say that the event keyword is a delegate modifier.
Let us consider some differences.
- Even though an event is a delegate, delegate declaration cannot replace event declaration. Delegates must be declared earlier in order to become an event.
- Event declaration does not specify the function signature, it uses the pre-defined type.
- A participant cannot subscribe to a delegate. You must declare (publish) the event in order to enable subscription.
- The event belongs to its class. You cannot invoke the event outside its class (as to a delegate – you can invoke it from any place).
- If you define an interface, you cannot declare a delegate as a part of the interface but you can do it for event.
- .NET applies some restriction on a delegate that you want to use as an event. It requires that the function signature was
void() (object sender, EventArgs e)
.
Code
MyFamilyEventHandler
is a delegate that we prepared to be a source for a future event:
public delegate void MyFamilyEventHandler(object sender, EventArgs e);
Family
class declares an event OnBabyBorn
. The method BabyBorn
raises the event. It checks if the event is not null
, in other words it checks that the event has subscribers.
The event has a type of MyFamilyEventHandler
.
The base class it inherits from is MulticastDelegate
(See Picture 1). This proves that an event is just a delegate by nature.
Here goes the Family
class:
public class Family
{
public event MyFamilyEventHandler OnBabyBorn;
public string Name { get; set; }
public Family(string name)
{
Name = name;
}
public void BabyBorn()
{
if (null != OnBabyBorn)
OnBabyBorn(Name, new EventArgs());
}
}
Now I created the Friend
class.
Friend
class has a public
property Family
.
When this property is being assigned (within Set procedure), the friend
subscribes for family
event OnBabyBorn
. Subscription is done by using the following code:
family.OnBabyBorn += new MyFamilyEventHandler(family_OnBabyBorn);
As you already know, when a compiler meets this code, it creates an instance of the event (which means a delegate) and assigns the reference of the function family_OnBabyBorn
to the delegate. From now on, when the event is raised (which means that it's the base delegate invoke
), the function is called and executed.
Remember, the delegate resides at family
class. So the friend provides the family
with his method information through the delegate that will be invoked when event occurs.
public class Friend
{
private Family family;
public Family Family
{
get
{
return family;
}
set
{
family = value;
family.OnBabyBorn += new MyFamilyEventHandler
(family_OnBabyBorn);
}
}
public string Name { get; set; }
public Friend(string name)
{
Name = name;
}
void family_OnBabyBorn(object sender, EventArgs e)
{
Console.WriteLine("{0}, go visit {1} family", Name, sender.ToString() );
}
}
Finally the Program
class.
It creates Family
object and two Friend
objects.
It assigns the family
to the friend
s. It runs family BabyBorn
procedure, which triggers the OnBabyBorn
event.
Both friend
s are notified that the event occurred and the friend
’s pre-assigned function is called.
class Program
{
static void Main(string[] args)
{
Family family = new Family("Adams");
Friend Ed = new Friend("Ed");
Ed.Family = family;
Friend Alex = new Friend("Alex");
Alex.Family = family;
family.BabyBorn();
Console.Read();
}
}
Here is the output:
Notes
By associating programming subjects (delegates and events) with real world subjects, I tried to show you that in your code, you should use similar logic. Delegates and events in programming have the same meaning as in the real world. I intentionally created a very simple example of how to use events. The idea is just to show common sense behind events in programming.
I hope this will help to better understand delegates and events and use them in your code.
The next article I am working on is to introduce you to the modern world of delegates.
To be continued...