DEV Community

mohamed Tayel
mohamed Tayel

Posted on

C# Advanced:Anonymous Functions, and Lambdas

  1. Anonymous Functions
  2. Lambda Expressions
  3. Differences Between Delegates, Anonymous Functions, and Lambdas
  4. Practical Examples
  5. When to Use Each Concept
  6. Advantages and Drawbacks
  7. Best Practices
  8. Assignments
  9. Conclusion

Anonymous Functions

Anonymous functions are methods without a name, defined inline. They provide a way to create delegates or lambda expressions without having to define a separate named method.

Types of Anonymous Functions:

  1. Lambda Expressions
  2. Anonymous Methods

Anonymous Methods (Pre-C# 3.0)

Before lambda expressions were introduced in C# 3.0, anonymous methods provided a way to define inline functions.

Notify notifier = delegate(string message)
{
    Console.WriteLine(message);
};

notifier("Hello from Anonymous Method!");
Enter fullscreen mode Exit fullscreen mode

Lambda Expressions

Lambda expressions are a concise way to represent anonymous functions using a special syntax. They are widely used in LINQ queries and event handling due to their brevity and readability.

Lambda Syntax

(parameters) => expression
Enter fullscreen mode Exit fullscreen mode
  • Parameters: Input parameters (can be omitted if not needed).
  • Lambda Operator (=>): Separates parameters from the body.
  • Body: Can be a single expression or a block of statements.

Examples

  1. Single Parameter, Single Expression

    Func<int, int> square = x => x * x;
    Console.WriteLine(square(5)); // Output: 25
    
  2. Multiple Parameters

    Func<int, int, int> add = (x, y) => x + y;
    Console.WriteLine(add(3, 4)); // Output: 7
    
  3. No Parameters

    Action greet = () => Console.WriteLine("Hello, Lambda!");
    greet(); // Output: Hello, Lambda!
    
  4. Multiple Statements

    Func<int, int, int> multiply = (x, y) =>
    {
        int result = x * y;
        return result;
    };
    Console.WriteLine(multiply(3, 4)); // Output: 12
    

Differences Between Delegates, Anonymous Functions, and Lambdas

Feature Delegate Anonymous Function Lambda Expression
Definition Type that references methods Inline method without a name Concise syntax for anonymous functions
Syntax public delegate void Notify(string msg); delegate(string msg) { ... } (params) => expression or (params) => { ... }
Usage Define a type-safe method reference Define inline method logic Define inline method logic with concise syntax
Introduced In C# 1.0 C# 2.0 C# 3.0

Practical Examples

Let's explore practical scenarios where delegates, anonymous functions, and lambda expressions are used.

Example 1: Using Delegates

Scenario: You have a list of numbers, and you want to perform different operations (like printing or doubling) on each number.

using System;
using System.Collections.Generic;

public delegate void NumberOperation(int number);

public class DelegateExample
{
    public static void PrintNumber(int number)
    {
        Console.WriteLine($"Number: {number}");
    }

    public static void DoubleNumber(int number)
    {
        Console.WriteLine($"Double: {number * 2}");
    }

    public static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
        NumberOperation operation;

        // Assign PrintNumber method to delegate
        operation = PrintNumber;
        foreach (var num in numbers)
        {
            operation(num);
        }

        Console.WriteLine("-----");

        // Assign DoubleNumber method to delegate
        operation = DoubleNumber;
        foreach (var num in numbers)
        {
            operation(num);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
-----
Double: 2
Double: 4
Double: 6
Double: 8
Double: 10
Enter fullscreen mode Exit fullscreen mode

Example 2: Anonymous Functions with Delegates

Scenario: Simplify the previous example by using anonymous methods instead of separate named methods.

using System;
using System.Collections.Generic;

public delegate void NumberOperation(int number);

public class AnonymousFunctionExample
{
    public static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
        NumberOperation operation;

        // Using anonymous method to print numbers
        operation = delegate(int num)
        {
            Console.WriteLine($"Number: {num}");
        };
        foreach (var num in numbers)
        {
            operation(num);
        }

        Console.WriteLine("-----");

        // Using anonymous method to double numbers
        operation = delegate(int num)
        {
            Console.WriteLine($"Double: {num * 2}");
        };
        foreach (var num in numbers)
        {
            operation(num);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
-----
Double: 2
Double: 4
Double: 6
Double: 8
Double: 10
Enter fullscreen mode Exit fullscreen mode

Example 3: Lambda Expressions in LINQ

Scenario: Use lambda expressions to filter and project data using LINQ.

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

public class LambdaExample
{
    public static void Main()
    {
        List<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date", "Elderberry" };

        // Using lambda to filter fruits that start with 'B' or later in the alphabet
        var filteredFruits = fruits.Where(f => string.Compare(f, "B", StringComparison.Ordinal) >= 0);

        Console.WriteLine("Filtered Fruits:");
        foreach (var fruit in filteredFruits)
        {
            Console.WriteLine(fruit);
        }

        Console.WriteLine("-----");

        // Using lambda to project fruits to uppercase
        var upperFruits = fruits.Select(f => f.ToUpper());

        Console.WriteLine("Uppercase Fruits:");
        foreach (var fruit in upperFruits)
        {
            Console.WriteLine(fruit);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Filtered Fruits:
Banana
Cherry
Date
Elderberry
-----
Uppercase Fruits:
APPLE
BANANA
CHERRY
DATE
ELDERBERRY
Enter fullscreen mode Exit fullscreen mode

When to Use Each Concept

  • Delegates: Use delegates when you need to define a type that can reference multiple methods with the same signature. They are especially useful for implementing callback methods and event handling.

  • Anonymous Functions: Use anonymous functions (including both anonymous methods and lambda expressions) when you need to pass a small piece of code (logic) to a method without cluttering your codebase with multiple small, single-use methods.

  • Lambda Expressions: Use lambda expressions for more concise and readable code, especially when working with LINQ queries, event handlers, or any scenario that benefits from inline function definitions.


Advantages and Drawbacks

Advantages

  • Flexibility: Delegates, anonymous functions, and lambdas allow methods to be passed as parameters, enabling flexible and reusable code.

  • Conciseness: Lambda expressions and anonymous functions reduce the need for boilerplate code, making the codebase cleaner and easier to maintain.

  • Enhanced Readability: When used appropriately, these features can make the code more readable by localizing the logic close to where it's used.

  • Functional Programming Support: They enable functional programming paradigms within C#, such as higher-order functions and LINQ.

Drawbacks

  • Complexity: Overusing or misusing delegates and lambdas can make the code harder to understand, especially for those unfamiliar with these concepts.

  • Debugging Difficulty: Anonymous functions can be harder to debug since they don't have names and are defined inline.

  • Performance Considerations: Excessive use of delegates and lambdas, especially in performance-critical sections, can introduce overhead due to additional allocations.


Best Practices

  1. Keep Lambdas Simple: Use lambda expressions for simple operations. If the logic becomes complex, consider extracting it into a separate named method for clarity.

    // Simple Lambda
    var evenNumbers = numbers.Where(n => n % 2 == 0);
    
    // Complex Lambda (Better as a separate method)
    var processedNumbers = numbers.Select(n => 
    {
        // Complex logic here
        return ProcessNumber(n);
    });
    
  2. Use Descriptive Parameter Names: Even though lambdas can have short parameter names, ensure they are descriptive enough to convey their purpose.

    // Less Descriptive
    var squares = numbers.Select(x => x * x);
    
    // More Descriptive
    var squares = numbers.Select(number => number * number);
    
  3. Avoid Capturing Unnecessary Variables: Be cautious about capturing variables from the surrounding scope to prevent unintended side effects and memory leaks.

  4. Prefer Expression Lambdas Over Statement Lambdas When Possible: Expression lambdas are more concise and generally easier to read.

    // Expression Lambda
    Func<int, int> square = x => x * x;
    
    // Statement Lambda
    Func<int, int> square = x =>
    {
        return x * x;
    };
    
  5. Understand Delegate Types: Familiarize yourself with built-in delegate types like Action, Func, and Predicate to simplify your code.

    // Using Func for a method that returns a value
    Func<int, int> square = x => x * x;
    
    // Using Action for a method that doesn't return a value
    Action<string> greet = message => Console.WriteLine(message);
    

Assignments


Easy Level Assignment

Task: Implement a program that uses a delegate to perform basic mathematical operations (addition, subtraction, multiplication, and division).

  • Define a delegate that can reference methods that accept two integers and return an integer.
  • Create methods for addition, subtraction, multiplication, and division.
  • Use the delegate to call these methods and print the results for two numbers provided by the user.

Medium Level Assignment

Task: Create a program that uses anonymous functions to filter and sort a list of student names.

  • Create a list of at least 5 student names.
  • Use an anonymous function to filter the names that start with a specific letter.
  • Use another anonymous function to sort the filtered names in alphabetical order.
  • Display the filtered and sorted list of names.

Difficult Level Assignment

Task: Implement a program that uses lambda expressions and LINQ to process a collection of products.

  • Create a list of products with properties such as Name, Price, and Category.
  • Use a lambda expression to filter products by category and price range.
  • Use LINQ with lambda expressions to sort the filtered products by price in descending order.
  • Output the filtered and sorted products to the console.

Conclusion

Delegates, anonymous functions, and lambda expressions are integral parts of modern C# programming, offering developers the tools to write flexible, concise, and maintainable code. By understanding and effectively utilizing these concepts, you can enhance your code's readability and functionality, making your applications more robust and efficient.

Remember to use these features judiciously to maintain code clarity and avoid unnecessary complexity. As with any powerful tool, the key lies in leveraging their strengths while being mindful of their limitations.

Top comments (0)