Events, Broadcasters, Subscribers and Event Patterns

Events, Broadcasters, Subscribers and Event Patterns

Events

Events enable classes to communicate and inform other classes when something relevant to happens.

When an action relevant to an event occurs, a special type of class, called a broadcaster or publisher, calls one or many different classes to deal with the event. These receiving classes are referred to as subscribers. These subscribers contain further logic as to how to deal with an event

Broadcaster and Subscriber model
A broadcaster object with many subscriber objects

This event-based method of object communication is a very common option when designing and implementing user interfaces. Socket and slot is another name for this design method.

A broadcaster might not have any associated subscribers and if this happens then no actions will be performed. An example of this might be a button that has no event handler.

Event Implementation

The broadcaster class contains a delegate field that is invoked when an event is triggered, the target for these delegates is a method from the subscriber classes. A subscriber can start and stop listening by adding and removing itself from the broadcasters delegate respectively.

Delegates can be modified by calling the += and -+ operations on the object.

To add an event to a class, the ‘event’ keyword should be added in front of a delegate instantiation like so:


                    using System;

public class Program
{
	public static void Main()
	{
		Broadcaster broadcaster = new Broadcaster();

		ValueHandler valueHandler, valueHandler2;

		valueHandler = TestFunctions.ReportValues;
		valueHandler2 = TestFunctions.ReportValues2;

		broadcaster.valueHandler += valueHandler;
		broadcaster.valueHandler += valueHandler2;

		broadcaster.TestValue = 2;

	}
}

public delegate void ValueHandler(int oldValue, int newValue);

public class Broadcaster
{
	int testValue = 0;

	public int TestValue
	{
		get {return testValue;}
		set
		{
			if(value > 0)
			{
				valueHandler(this.testValue, value);
			} else {
				return;
			}
		}
	}

	public event ValueHandler valueHandler;
}

public static class TestFunctions
{
	public static void ReportValues(int o, int n)
	{
		Console.WriteLine($"The old value was {o}, the new value is {n}.");
	}

	public static void ReportValues2(int o, int n)
	{
		Console.WriteLine($"The old value was {o}, the new value is {n}. Second function");
	}

}
                

Normally, an event would be triggered in response to a piece of data being changed. Here we will introduce a new value to the Broadcaster class and configure the setter to trigger the event.


                    using System;

public class Program
{
	public static void Main()
	{
		Broadcaster broadcaster = new Broadcaster();

		ValueHandler valueHandler, valueHandler2;

		valueHandler = TestFunctions.ReportValues;
		valueHandler2 = TestFunctions.ReportValues2;

		broadcaster.valueHandler += valueHandler;
		broadcaster.valueHandler += valueHandler2;

		broadcaster.TestValue = 2;

	}
}

public delegate void ValueHandler(int oldValue, int newValue);

public class Broadcaster
{
	int testValue = 0;

	public int TestValue
	{
		get {return testValue;}
		set
		{
			if(value > 0)
			{
				valueHandler(this.testValue, value);
			} else {
				return;
			}
		}
	}

	public event ValueHandler valueHandler;
}

public static class TestFunctions
{
	public static void ReportValues(int o, int n)
	{
		Console.WriteLine($"The old value was {o}, the new value is {n}.");
	}

	public static void ReportValues2(int o, int n)
	{
		Console.WriteLine($"The old value was {o}, the new value is {n}. Second function");
	}

}
                

Standard .NET Event Patterns

There are a number of standard design patterns used throughout .NET, one being the standard for implementing events.

To ensure there is consistency between user code and available frameworks. .NET includes a standard event class that can be inherited from. This class is System.EventArgs.

To see more information on the EventArgs class, visit the Microsoft documentation:

https://docs.microsoft.com/en-us/dotnet/api/system.eventargs?view=netcore-3.1

The EventArgs class does not contain any members and instead outlines the structure for a standard event. When you create a class that inherits from the System.EventArgs class, you should name it in relation to the data that it will impact rather than the event it will be used in conjunction with. As well as this, the class name should end with “EventArgs” to demonstrate it’s application.


                    using System;

public class Program
{
	public static void Main()
	{
	}
}

public class WeightChangeEventArgs : System.EventArgs
{
	public readonly int OldWeight;
	public readonly int NewWeight;

	public WeightChangeEventArgs(int oldWeight, int newWeight)
	{
		OldWeight = oldWeight;
		NewWeight = newWeight;
	}
}
                

A delegate must be defined to use in conjunction with the event. The delegate must have a void return type, end with EventHandler and accept two arguments, one of which must be a subclass of EventArgs.

There is a generic delegate already defined under System.EventHandler<>.

To finish off, we must define a virtual method that will be triggered when the event fires. The name for this should be the same as the event and have the word “On” before it.


                    using System;


public class Program
{
	public static void Main()
	{
	}
}



public class Fruit
{
	public event EventHandler<WeightChangeEventArgs> WeightChanged;

	public int OldWeight;
	public int NewWeight;

	protected virtual void OnWeightChange(WeightChangeEventArgs e)
	{
		WeightChanged(this,e);
	}
}

public class WeightChangeEventArgs : System.EventArgs
{
	public readonly int OldWeight;
	public readonly int NewWeight;

	public WeightChangeEventArgs(int oldWeight, int newWeight)
	{
		OldWeight = oldWeight;
		NewWeight = newWeight;
	}
}