The first techniques mentioned
Usually, when I ask developers (in my trainings) how they implement extensibility mechanisms, I often have answers about class inheritance and method overload.
These two mechanisms are of course possible, but they are not always the simplest and most effective to use.
The use of inheritance or overload will especially complicate your code. The next developer who will have to read your code will have more difficulty understanding and reusing what you have written.
In the beginning, I also used the inheritance (a little too much besides :-)) and the overloads of methods. From now on, I also use other techniques that make it possible to do things differently and better (in my opinion).
I spoke of the principle in the previous article on delegates in csharp. So I’ll quickly summarize what is possible here and extend the mechanism to events.
The principle
Delegates are references to methods in C #. The idea is to use these references as an extensibility mechanism.
Providing an extensibility mechanism in your code will allow an existing behavior to evolve without changing the code that performs this behavior.
The idea behind all this is simple: once a piece of code tested and validated, we must try not to touch it.
To add a new behavior, it will be possible to use the delegates in C # or the events.
Extensibility thanks to delegates in CSharp
Delegates allow references to “external” methods that can be written later.
So, if you write a method, you can call delegates to perform operations in your code.
The most common use of delegates is to be able to choose between one algorithm and another (design pattern Strategy).
I suggest you discover an example that will allow you to understand. At first, I start by writing a basic algorithm (in the example, it adds a random number to each element of an array of integers).
1 2 3 4 5 6 7 8 9 10 11 |
void Algorithm(int[] numbers) { Random rnd = new Random(); for (int n = 0; n < numbers.Length; n++) { numbers[n] = numbers[n] + rnd.Next(0, 1); } } |
By adding a delegate as a parameter, I can propose a behavior that allows to add a parameterizable filter (which joins the Strategy pattern pattern).
Here is the corresponding code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
delegate int[] FiltreDelegate(int[] nombres); void Algorithme(int[] nombres, FiltreDelegate filtre) { Random rnd = new Random(); // Point d'extensibilité if (filtre != null) nombres = filtre(nombres); for (int n = 0; n < nombres.Length; n++) { nombres[n] = nombres[n] + rnd.Next(0, 1); } } |
Scalability thanks to multicast delegates in CSharp
Multicast delegates are a special type of delegates. In fact, it is possible to assign more than one method reference to a single delegate.
This will allow you to call several methods when you call your delegate.
The cool thing is that a classic delegate can also be used as a multicast delegate without changing the code of your algorithm.
To add multiple methods to a delegate, just use the “+” operator. To delete a method, the operator “-“.
I propose you to adapt the previous code to be able to use several filters on the numbers in input. It will simply be necessary to pay attention to the order of the calls (the order of addition of the delegates).
Here is the code adapted:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
class Program { static void Main(string[] args) { int[] entry = new int[] { 1, 2, 3 }; Program p = new Program(); FilterDelegate dlg = new FilterDelegate(p.SimpleFilter); // Add the second delegate dlg + = p.SecondFilter; int[] result = p.Algorithm(input, dlg); Console.Read(); } void SimpleFilter(ref int[] numbers) { List result = new List(numbers); resultat.Remove(1); numbers = result.ToArray(); } void SecondFilter(ref int[] numbers) { List copy = new List(numbers); copie.Reverse(); numbers = copy.ToArray(); } delegate void FilterDelegate(ref int[] numbers); int[] Algorithm(int[] numbers, FilterDelegate filter) { Random rnd = new Random(); // Extensibility point if (filter! = null) filter(ref numbers); for (int n = 0; n < numbers.Length; n++) { numbers[n] = numbers[n] + rnd.Next(0, 5); } return numbers; } } |
There is a little subtlety so I have to talk to you: I used the keyword “ref” in the delegate.
This is necessary so that each delegate can “work” on the same set of data.
This mechanism makes it possible to call the Algorithm method with two filters:
The first SimpleFilter filter to delete items whose value is 1,
The second SecondFilter filter inverts the order of the elements.
Extensibility with events
Finally, the third method I wanted to talk about uses events in C # (key word event).
Events in C # allow to set up “recall” mechanisms at the level of the class. The classes you write can warn subscribers that something has happened.
event is a keyword that allows to declare a property of the type delegate multicast (this also allows events to be used interfaces).
In the .NET framework, events are used everywhere (especially in graphical controls).
For example, on the Button class, you have a “Click” event. This will be launched when a user clicks a button.
This mechanism will call several methods to make a treatment.
There is a little subtlety with events: only the class that declares the events can launch its events.
It will therefore write code in your class to launch the appropriate events.
Here is an adaptation of the previous algorithm example (I put the set in a class after a bit of refactoring).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
// class Algorithm: public class Algorithm { public delegate void FilterDelegate(ref int[] numbers); public event FilterDelegate BeforeCalculating; public event FilterDelegate AfterCalculation; public int[] Calculate(int[] numbers) { Random rnd = new Random(); // Extensibility point // modify the data before the calculation if (BeforeCalcul! = null) BeforeCalculation(ref numbers); for (int n = 0; n < numbers.Length; n++) { numbers[n] = numbers[n] + rnd.Next(0, 2); } // Extensibility point // modify the data after the calculation if (AfterCalculation = null) AfterCalculation(ref numbers); return numbers; } } // class Program (entry point of the console application) class Program { static void Main(string[] args) { int[] entry = new int[] { 1, 2, 3, 4, 5 }; Algo algorithm = new Algorithm(); Program p = new Program(); // two filters before the calculation algo.BeforeCalculation + = p.SimpleFilter; algo.BeforeCalculation + = p.SecondFilter; // a filter after algo.ApresCalcul + = p.SecondFilter; int[] result = algo.Calculate(input); Console.WriteLine(String.Join("", result)); } void SimpleFilter(ref int[] numbers) { List result = new List(numbers); resultat.Remove(1); numbers = result.ToArray(); } void SecondFilter(ref int[] numbers) { List copy = new List(numbers); copie.Reverse(); numbers = copy.ToArray(); } } |
We therefore note that the events make it possible to set up extension mechanisms at the level of the class whereas the delegates are used to extend methods.