Welcome to the fourth episode of my course “Becoming a software developer”, which will give you a quick overview of the more advanced concepts of the C# language that allows writing a really neat and composable code.
All of the materials including videos and sample projects can be downloaded from here.
Scope
This episode will focus on the the very interesting parts of the language:
- Delegates
- Lambda expressions
- Events
Abstract
Delegate is a sort of pointer to the method. Do you remember the interfaces (well, you should)? By using delegate you can also specify such form of “contract”, but in this case, it points to the method. Consider the following example:
1 2 3 4 5 6 7 8 9 10 |
public delegate int Add(int x, int y); public int AddTwoNumbers(int a, int b) { return a + b; } Add adder = AddTwoNumbers; int result = adder(1,2); //3 |
So, what happened here? At first there’s a delegate named “Add” – it has its unique name, type that is being returned (you can also use void) and the method parameters (0 or more). Below is some typical method with its body. And finally, we’re assigning our variable adder being our Add delegate to the AddTwoNumbers method. And why we can do that? Because, both, the type that is being returned int and the method parameters int and int do match. By having such adder variable we can invoke (or call) it as it actually points to our method and will result in adding two numbers. It’s not that complicated right?
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 66 67 68 |
public class Delegates { public delegate void Write(string message); public delegate int Add(int x, int y); public delegate void Alert(int change); public void Test() { Write writer = WriteMessage; writer("Writer delegate."); Add adder = AddTwoNumbers; var sum = adder(1,2); Console.WriteLine($"Add delegate has returned the sum of the two numbers: {sum}." ); CheckTemperature(TooLowAlert, OptimalAlert, TooHighAlert); } public static void WriteMessage(string message) { Console.WriteLine($"Writer: {message}"); } public static int AddTwoNumbers(int a, int b) { return a + b; } public static void TooLowAlert(int change) { Console.WriteLine($"Temperature is too low (changed by {change})."); } public static void OptimalAlert(int change) { Console.WriteLine($"Temperature is optimal (changed by {change})."); } public static void TooHighAlert(int change) { Console.WriteLine($"Temperature is too high (changed by {change})."); } public static void CheckTemperature(Alert tooLow, Alert optimal, Alert tooHigh) { var temperature = 10; var random = new Random(); while(true) { var change = random.Next(-5,5); temperature += change; Console.WriteLine($"Temperatue is at: {temperature} C."); if(temperature <= 0) { tooLow(change); } else if(temperature > 0 && temperature <= 10) { optimal(change); } else { tooHigh(change); } Thread.Sleep(500); } } } |
Now, what might be the real use of this weird concept? Well, what about passing the
Lambda expression is the evolution of delegate. You can declare the anonymous methods that can do pretty much anything (under the hood they’re a normal methods with input parameters and a type that is being returned). Take a look at this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Action showMyName = () => Console.WriteLine("Piotr"); Action showMyNameAgain = () => { Console.WriteLine("Piotr") }; Action<string> greetMyName = x => Console.WriteLine($"{x} Piotr"); Func<string> getMyName = () => "Piotr"; Func<string> getMyNameAgain = () => { return "Piotr"; } Func<string, string> greetAndGetMyName x => "{x} Piotr"; Func<int,int,int> addTwoNumbers = (x,y) => x + y; Func<int,int,int> addTwoNumbersAgain = (x,y) => { return x + y; } sayMyName(); //Piotr sayMyNameAgain(); //Piotr greetMyName("Hello"); //Hello Piotr var myName = getMyName(); //Returns Piotr var myNameAgain = getMyNameAgain(); //Returns Piotr var myNameWithGreeting = greetAndGetMyName("Hello"); //Returns Hello Piotr var addition = addTwoNumbers(1,2); // Returns 3 var additionAgain = addTwoNumbersAgain(1,2); // Returns 3 |
What happened here? We’ve declared an
Let’s take a look at the more sophisticated examples.
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 |
public class LambdaExpressions { public delegate void Writer(); public void Test() { Writer writer = Write; Action writer = () => Console.WriteLine("Writing..."); Action<string,int> advancedWriter = (str,num) => Console.WriteLine($"{str}, {num}"); writer(); advancedWriter("Hello!", 1); Func<int, int, int> adder = (x,y) => x + y; var sum = adder(1,2); Console.WriteLine($"Add lambda expression has returned the sum of the two numbers: {sum}." ); Action<int, string> logger = (t, m) => { Console.WriteLine($"Temperature is at: {t}. {m}"); }; CheckTemperature(t => logger(t, "Too low!"), t => logger(t, "Optimal."), t => logger(t, "Too high!")); } public static void Write() { Console.WriteLine("Writing..."); } public static void CheckTemperature(Action<int> tooLow, Action<int> optimal, Action<int> tooHigh) { var temperature = 10; var random = new Random(); while(true) { var change = random.Next(-5,5); temperature += change; if(temperature <= 0) { tooLow(temperature); } else if(temperature > 0 && temperature <= 10) { optimal(temperature); } else { tooHigh(temperature); } Thread.Sleep(500); } } } |
Treating functions as the before mentioned first class members and being able to declare them anonymously using lambda expressions is one of the most powerful features of the C# language. It can be used for filtering the collections, transforming objects between different types, creating custom validation rules and so many other great things, that literally, sky is the limit here. Trust me on this and I strongly encourage you to play with this concept for a long time, in order to truly understand it. Then it will become a natural pattern that you’ll be able to apply whenever it makes sense.
And these special objects are pointers to the functions, just like the delegate – you can see the beauty and simplicity of using such inline and anonymous methods?
If you’re still uncertain what is the practical use of this concepts, let me give you one of the many examples that you will discover in the next episode:
Events
The last concept that we’re talking about will be events. It’s a special keyword that you can use in order to make use of the events that may occur within your application.
Events are of course connected to the delegates, and there’s a special type EventHandler
We’re not going to use them too often (if at all) within the web application as they’re stateless (unlike the desktop application, where the events are heavily used).
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 66 67 68 69 |
public class Events { public delegate void UpdateStatus(string status); public event UpdateStatus StatusUpdated; public EventHandler<StatusEventArgs> StatusUpdatedAgain; public void Test() { } public void StartUpdatingStatus() { while(true) { if(StatusUpdated != null) { StatusUpdated($"status with ticks {DateTime.UtcNow.Ticks}"); } StatusUpdated?.Invoke($"status with ticks {DateTime.UtcNow.Ticks}"); StatusUpdatedAgain?.Invoke(this, new StatusEventArgs("Status event args")); Thread.Sleep(500); } } } public class StatusEventArgs : EventArgs { public string Status { get; } public StatusEventArgs(string status) { Status = status; } } public class FunctionalSandbox { public void Test() { var delegates = new Delegates(); delegates.Test(); var lambdaExpressions= new LambdaExpressions(); lambdaExpressions.Test(); var events = new Events(); events.StatusUpdated += s => { Console.WriteLine($"Received: {s}"); }; events.StatusUpdated += DisplayStatus; events.StatusUpdated += DisplayStatus2; events.StatusUpdatedAgain += (sender, eventArgs) => { Console.WriteLine($"Event handler callback: {eventArgs.Status}"); }; events.StartUpdatingStatus(); } public void DisplayStatus(string status) { Console.WriteLine($"Received: {status}"); } public void DisplayStatus2(string status) { Console.WriteLine($"Received again: {status}"); } } |
Resources
- https://www.youtube.com/watch?v=ifbYA8hyvjc
- https://www.youtube.com/watch?v=jQgwEsJISy0
- https://www.tutorialspoint.com/csharp/csharp_delegates.htm
- http://stackoverflow.com/questions/2019402/when-why-to-use-delegates
- http://csharp-station.com/Tutorial/CSharp/Lesson14
- https://msdn.microsoft.com/pl-pl/library/windows/desktop/bb397687(v=vs.90).aspx
- https://www.dotnetperls.com/lambda
- http://stackoverflow.com/questions/167343/c-sharp-lambda-expressions-why-should-i-use-them
- https://www.codeaddiction.net/articles/13/lambda-expressions-delegates-predicates-and-closures-in-c
- https://weblogs.asp.net/dixin/understanding-csharp-features-5-lambda-expression
- https://lostechies.com/gabrielschenker/2009/02/03/step-by-step-introduction-to-delegates-and-lambda-expressions/
- https://www.tutorialspoint.com/csharp/csharp_events.htm
- http://stackoverflow.com/questions/803242/understanding-events-and-event-handlers-in-c-sharp
- http://csharpindepth.com/Articles/Chapter2/Events.aspx
Next
- Debugging in a nutshell
- Extension methods
- IEnumerable
- yield
- IQueryable
- LINQ
In the next episode, we’ll dive into the LINQ and make use of our anonymous methods and objects in order to make the sophisticated queries for filtering and transforming collections of data. Eventually, you’ll see the difference between the IEnumerable and IQuerabyle and what is all the fuss about the yield keyword.
Pingback: Becoming a software developer – episode IV – Patryk Huzarski | Personal Blog
Pingback: Dew Drop - February 16, 2017 (#2423) - Morning Dew
This is awesome Piotr. I am an English speaking audience and loved reading your write-up. I’m feeling encouraged to play around with delegates more – in more situations than just events. Keep it up!
Thank you very much! I wish you could watch the videos, yet we’re recording them using the Polish language in order to make sure that our local community gets the most benefits out of it. However, maybe one day I’ll be able to include subtitles or record them again in English, so they could provide valuable content for you and other non-polish speakers :).
Hi Piotr ! When you talk about lambda expressions, there are (in code) methods which you invoke as “sayMyName” and “sayMyNameAgain”. Shouldn’t be there “showMyName” and “showMyNameAgain”? Anyway, perfect job!
Yes, you’re probably right, thanks :).
Witaj Piotrku,
Wyjaśnij mi proszę jaka jest różnica między użyciem lub nie słowa kluczowego “event” ponieważ wałkuję temat delegatów i eventów jakiś czas i nie potrafię znaleźć między nimi różnicy…
Przykładowo
Delegat:
public delegate void UpdateStatus(string status);
public UpdateStatus StatusUpdated;
public event UpdateStatus StatusUpdatedE;
Zarówno dla StatusUpdated oraz StatusUpdatedE możemy subskrybować metody pasujące dla delegata, dla obu przypadków możemy wykonać Invoke(), gdzie więc leży ta zasadnicza różnica między tymi przypadkami?
Dodatkowo czy w powyższym listeningu w kogdzie (5 linia) nie powinno być właśnie słowa kluczowego event?
public event EventHandler StatusUpdatedAgain;
Pozdrawiam, Paweł
Hi, Piotr!
Just one question, how do you think, is it possible to become software developer for a girl with humanitarian mind? Or this is just a waste of time? ))) I just want to start learning c3 and found the resource, where they say you learn by reading a story… i guess, it may be helpful if i’m afraid if dry theory… thanks in advance!
Hi Julie,
I know people, with the so-called humanitarian mind, who studied totally different subjects than IT and are great developers anyway. I do believe that it all boils down to the passion and at least a little bit of the typical analyst mind that is quite common for the most of us :).