C# Delegates, Actions, Funcs, Lambdas–Keeping it super simple

 

C# Delegates, Actions, Funcs, Lambdas–Keeping it super simple

Rate This
  • Comments 9
Syntactic Sugar? Maybe.
[ Updated to reflect a reader’s observation – missing Func<> coverage. Now included ]
I was speaking with a colleague of mine today, Bret Stateham (http://bretstateham.com) and I was explaining my ignorance to some well established C# language constructs. I’ve always avoided language constructs that translate into “less typing.” What I mean is, delegates, actions, and lambdas can be totally avoided and you can still build the most sophisticated software. The compiler steps in at compile time and generates the IL for you (intermediate language), that language that gets translated into CPU-specific machine language at runtime by the CLR (Common language runtime).
Conceptually, and for the most part, it looks like Figure 1 below. The “Regular C# code” is probably skipped, meaning the fancy C# code ends up directly as IL. Anders Hejlsberg, the creator of C# (and the former creator of Borland’s Turbo Pascal) has spoken in detail about the inter-workings of the C# compiler in the past.
Figure 1
l2t4pq0a
[ BTW, one day I’ll talk about what an amazing human being Anders is. He is the most humble, approachable, computer science genius I’ve ever met ].
Anders Hejlsberg Interview http://www.microsoft.com/downloads/details.aspx?FamilyID=B202A125-DC9C-495A-8A5A-7BF98BECACE2&displaylang=e&displaylang=en
Let’s start with a simple delegate example
A delegate is a type that safely encapsulates a method, similar to a function pointer in C and C++. Unlike C function pointers, delegates are object-oriented, type safe, and secure. The type of a delegate is defined by the name of the delegate.
Code is the best teacher.
Part 1 Helps the compiler with type safety
Part 2 One of the methods associated with the delegate
Part 3 The other method associated with the delegate
Part 4 Our object used to demo delegates with
Part 5 Declare a delegate and attach a method from the demo object
Part 6 Declare a delegate and attach the other method from the demo object
Part 7 Exercise the first delegate. In other words, use it to do work.
Part 8 Exercise the second delegate. In other words, use it to do work.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        // Part 1 - Explicit declaration of a delegate (helps a compiler ensure type safety)
        public delegate double delegateConvertTemperature(double sourceTemp);

        // A sample class to play with
        class TemperatureConverterImp
        {
            // Part 2 - Will be attached to a delegate later in the code
            public double ConvertToFahrenheit(double celsius)
            {
                return (celsius * 9.0/5.0) + 32.0;
            }

            //  Part 3 - Will be attached to a delegate later in the code
            public double ConvertToCelsius(double fahrenheit)
            {
                return (fahrenheit - 32.0) * 5.0 / 9.0;
            }
        }


        static void Main(string[] args)
        {
            //  Part 4 - Instantiate the main object
            TemperatureConverterImp obj = new TemperatureConverterImp();

            //  Part 5 - Intantiate delegate #1
            delegateConvertTemperature delConvertToFahrenheit =
                         new delegateConvertTemperature(obj.ConvertToFahrenheit);

            //  Part 6 - Intantiate delegate #2
            delegateConvertTemperature delConvertToCelsius =
                         new delegateConvertTemperature(obj.ConvertToCelsius);

            // Use delegates to accomplish work

            //  Part 7 - delegate #1
            double celsius = 0.0;
            double fahrenheit = delConvertToFahrenheit(celsius);
            string msg1 = string.Format("Celsius = {0}, Fahrenheit = {1}",
                                         celsius, fahrenheit);
            Console.WriteLine(msg1);

            //  Part 8 - delegate #2
            fahrenheit = 212.0;
            celsius = delConvertToCelsius(fahrenheit);
            string msg2 = string.Format("Celsius = {0}, Fahrenheit = {1}",
                                         celsius, fahrenheit);
            Console.WriteLine(msg2);
        }
    }
}

image

C# Actions – More sugar, please

You can use the Action(Of T) delegate to pass a method as a parameter without explicitly declaring a custom delegate. The sugar here is you don’t have to declare a delegate. The compiler is smart enough to figure out the proper types.

But you pay a price in terms of a limitation. The corresponding method action must not return a value. (In C#, the method must return void.)

Part 1 The Action syntax avoids the use of a declared delegate. Everything is inline.
Part 2 The Action syntax avoids the use of a declared delegate. Everything is inline.
Part 3 Execute the corresponding Action code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Part 1 - First action that takes an int and converts it to hex
            Action<int> displayHex = delegate(int intValue)
            {
                Console.WriteLine(intValue.ToString("X"));
            };

            // Part 2 - Second action that takes a hex string and 
            // converts it to an int
            Action<string> displayInteger = delegate(string hexValue)
            {
                Console.WriteLine(int.Parse(hexValue,
                    System.Globalization.NumberStyles.HexNumber));
            };
            
            // Part 3 - exercise Action methods
            displayHex(16);
            displayInteger("10");
        }
    }
}
          
image

Func<> Delegates
This differs from Action<> in the sense that it supports parameters AND return values.
You can use this delegate to represent a method that can be passed as a parameter without explicitly declaring a custom delegate. The encapsulated method must correspond to the method signature that is defined by this delegate.
This means that the encapsulated method must have one parameter that is passed to it by value, and that it must return a value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Part 1 - First Func<> that takes an int and returns a string
            Func<int, string> displayHex = delegate(int intValue)
            {
                return (intValue.ToString("X"));
            };

            // Part 2 - Second Func<> that takes a hex string and 
            // returns an int
            Func<string, int> displayInteger = delegate(string hexValue)
            {
                return (int.Parse(hexValue,
                    System.Globalization.NumberStyles.HexNumber));
            };

            // Part 3 - exercise Func<> delegates
            Console.WriteLine(displayHex(16));
            Console.WriteLine(displayInteger("10"));
        }
    }
}

image
 

Lambdas – Syntactic Sugar Squared
I’ve been staring at Lambdas for years and for whatever reason they don’t come natural to me. Maybe I need to spend more time in a functional language like F# to make them a natural construct.
A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types.
All lambda expressions use the lambda operator =>, which is read as "goes to". The left side of the lambda operator specifies the input parameters (if any) and the right side holds the expression or statement block.
The lambda expression x => x * x is read "x goes to x times x."
Part 1 Declare 2 lambda expressions
Part 2 Run them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Part 1 - An action and a lambda
            Action<int> displayHex = intValue =>
            {
                Console.WriteLine(intValue.ToString("X"));
            };

            Action<string> displayInteger = hexValue =>
            {
                Console.WriteLine(int.Parse(hexValue,
                    System.Globalization.NumberStyles.HexNumber));
            };

            // Part 2 - Use the lambda expressions
            displayHex(16);
            displayInteger("10");

        }
    }
}
        
image


Lambdas and Queries
Lambda expressions can also be used to simplify queries.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Part 1 - ordinary list object
            List<string> listPets = new List<string>();

            // Part 2 - Queryable list object

            IQueryable<string> queryPets = listPets.AsQueryable();
            listPets.Add("dog");
            listPets.Add("cat");
            listPets.Add("iguana");

            // Part 3 - Lambda Expression (does not use curly braces)
            string result1 = listPets.First(x => x.StartsWith("d"));
            Console.WriteLine(result1);  // Prints "dog"

            // Part 4 - Lambda expressions using iQueryable interface
            string result2 = queryPets.First(x => x.StartsWith("ig"));
            Console.WriteLine(result2);  // Prints "iguana"

            // Part 5 - Lambda Statement (uses curly braces)
            //          Supports the return statement
            result1 = listPets.First(x => { return x.EndsWith("a"); });
            Console.WriteLine(result1);  // Prints "iguana"

            // Part 6 - Does not compile
            // A lambda expression with a statement body 
            // cannot be converted to an expression tree    
            // result2 = queryPets.First(x => { return x.EndsWith("e"); }); 

            // Part 7 - Does compile using the Func<T> syntax
            //          You can pass in a lambda expression and it 
            //          will be compiled to an Expression(Of TDelegate).
            string result3 = queryPets.First((Func<string, bool>)
                                         (x => { return x.EndsWith("g"); }));
            Console.WriteLine(result3);  // Prints "dog"


            // Part 8 - Convert to IQueryable
            IEnumerable<string> result4 =
                listPets.AsQueryable().Where(pet => pet.Length == 3);

            foreach (string pet in result4)
                Console.WriteLine(pet);  // Prints "dog" then "cat"


        }

    }
}
        
Want to help?
If you’ve got some super simple examples to demonstrate advance language features, please forward them to bterkaly@microsoft.com.
The next dragon I want to slay is Dependency Injection and Inversion of Control. I want to explain these two concepts in as little code as possible. Hope you got some value out of this post.

Download for Azure SDK


  • One intermediate piece you missed is that Func<T, U> (and its several variants) is the relative to Action<T> that provides a common, generic reusable delegate definition with a return value (the last in the list of generic type arguments). You'll notice in the Visual Studio tooltips that all of the Linq query operators use Func<T, U>.

  • I got value out this post for sure!  And have started to use the learnings in my blog at:

    http://bit.ly/zZjqi5

    Thank you for this well written blog Bruno!  It is exactly what I have been looking for.

  • Syntactic Sugar Squared. I love it. My only recommendation to readers would be to not try to nest and compound lambda operations in a single line - just because you can. The reality is this is complex stuff - we should not avoid it because it is complex (there are many good reasons to prefer lambda approaches) - but we should not over-complicate either.

    Crushing operations onto a single line makes readability a real maintenance nightmare. Try to have some love for the developer who will ultimately pick up your code some day later. I have done this myself, I admit. It's embarrassing when you get that call, "Um, could you step me through this one line?" I could have made it so much easier.

    It was also interesting to me so see how the lambda predicate can be deconstructed programatically. Perhaps your readers would benefit from the same: blog.jerrynixon.com/.../building-lambda-expression-tree.html

  • Bruno, I also wanted to "sell" you on the benefit of lambda in two ways. Your perspective of avoiding the "less typing" constructs is common and I think sometimes we overlook these:

    1. Lines of code are proportional to number of bugs. This is pretty well established. Steven Sinofsky says the ration is 1:10. And that's not 10 bugs for every line of code ;) Linq and Lambda reduce the number of lines (I think) exponentially.

    2. As the compiler changes, it will compile Linq and lambda better. Without changes to your code, the resulting IL can improve - performance, memory, parallelism, anything. THe compiler can do it because it understands Linq and Lambda clearly, where the intent of a typical FOR loop is a mystery to the compiler. That alone might be the number one reason to use it.

    Honestly, there is a third reason - it's just common now, so bite the bullet. I had a peer once say that Lambda and Linq "solve a problem I do not have". But he only said that because he didn't understand what they really did and what they really had to offer. Syntax-du-jour is not my point. Some things come and go. Linq and Lambda are revolutionary improvements to the language.

  • As long as there are limitations when debugging lambdas, I'd try balance the use that in mind.

  • Great article. One question. The title says: "C# Delegates, Actions, Funcs, Lambdas–Keeping it super simple". I am experiencing it now first-hand in a new job (i.e I never used these constructs before but they use it extensively here..)  saying 'Keeping it simple' Did you mean it is simpler with or without using C# Delegates, Actions, Funcs ?  

  • Whats about performance ?

    Action/Func are implemented as delegates. Delegates are implemented in IL as compiler-generated classes with an Invoke() method. Calling foo() when foo is a delegate actually compiles down to calling foo.Invoke(), which in turn calls the destination code. If foo is an actual method instead of a delegate, calling foo() calls directly to the destination code with no Invoke() intermediate. See ILDASM for proof.

    stackoverflow.com/.../206730

  • None of this makes any sense... I just don't get code!

  • In Lambdas – Syntactic Sugar Squared you used action but got a return value? Action should be Func right?

Page 1 of 1 (9 items)
Leave a Comment
  • Please add 8 and 8 and type the answer here:
  • Post