Skip to main content

Command Palette

Search for a command to run...

Mastering C# Part 3.2 Methods (Part-2)

Anonymous , Partial, Extension, Local Methods, Delegates, Predicates, Actions, Func

Updated
8 min read
Mastering C# Part 3.2 Methods (Part-2)

Anonymous Method

An anonymous method in C# is a method without a name, defined using the delegate keyword. It allows you to create inline methods, primarily useful in scenarios where a method implementation is required only once, such as for short event-handling or callback operations.


Syntax

delegate(parameter_list)
{
    // Method body
};

Anonymous methods must be assigned to a delegate type variable or used directly as a parameter for a method that expects a delegate.


using System;

class Program
{
    delegate int Operation(int x, int y); // Delegate definition

    static void Main()
    {
        // Assigning an anonymous method to a delegate
        Operation add = delegate (int a, int b)
        {
            return a + b;
        };

        int result = add(5, 3);
        Console.WriteLine($"The sum is: {result}"); // Output: The sum is: 8
    }
}

Key Characteristics

  1. Inline Declaration: Anonymous methods are defined inline where they are used.

  2. Access to Outer Variables: Anonymous methods can capture variables from the outer scope, known as closures.

  3. Event Handlers: Often used to simplify event handling code.


Using Anonymous Methods in Event Handling

Instead of defining a separate method for event handling, you can use an anonymous method directly:

using System;

class Program
{
    static void Main()
    {
        Action<string> print = delegate (string message)
        {
            Console.WriteLine(message);
        };

        print("Hello, World!"); // Output: Hello, World!
    }
}

Capturing Variables (Closures)

Anonymous methods can capture and use variables from the scope in which they are defined:

using System;

class Program
{
    delegate void PrintDelegate();

    static void Main()
    {
        int count = 10;

        // Anonymous method capturing the outer variable 'count'
        PrintDelegate print = delegate
        {
            Console.WriteLine($"Count is: {count}");
        };

        print(); // Output: Count is: 10

        count = 20;
        print(); // Output: Count is: 20 (shows the updated value)
    }
}

Comparison with Lambda Expressions

Lambda expressions were introduced in C# 3.0 as a more concise alternative to anonymous methods.

Anonymous Method:

delegate (int a, int b)
{
    return a + b;
};

Lambda Expression (Preferred):

(a, b) => a + b;

Key Differences:

  1. Syntax: Lambda expressions have a shorter, more concise syntax.

  2. Flexibility: Lambdas support advanced features such as expression trees and type inference.

  3. Preference: Lambdas are generally preferred over anonymous methods for modern C# development.


Anonymous Methods and Delegates

Anonymous methods are tightly coupled with delegates. For example:

using System;

class Program
{
    delegate void GreetDelegate(string name);

    static void Main()
    {
        GreetDelegate greet = delegate (string name)
        {
            Console.WriteLine($"Hello, {name}!");
        };

        greet("John"); // Output: Hello, John!
    }
}

Limitations of Anonymous Methods

  1. No Named Reusability: Anonymous methods cannot be reused elsewhere since they lack a name.

  2. Reduced Readability: Overusing anonymous methods can make code harder to understand.

  3. Verbose: Compared to lambda expressions, anonymous methods are more verbose.


When to Use Anonymous Methods

  1. Event Handling: When a short, one-time-use method is needed for an event.

  2. Temporary Delegates: For scenarios where defining a named method would be overkill.

  3. Testing and Prototyping: Quick method implementations during development.


Partial Methods in C

Partial methods allow optional method declarations and implementations in partial classes or partial structs. These methods enable flexibility when splitting a class across multiple files, commonly used in scenarios like auto-generated code.


Key Points

  1. Declaration and Implementation:

    • Declared in one part of the partial class.

    • Optionally implemented in another part.

    • If not implemented, the declaration is removed during compilation.

  2. Rules:

    • Must be private (default access).

    • Cannot have a return type (must be void).

    • Cannot be virtual, abstract, or extern.

  3. No Implementation? No Overhead!

    • Unimplemented partial methods are ignored by the compiler.

Declaration and Call:

partial class Employee
{
    partial void OnCreated(); // Declared
}

partial class Employee
{
    partial void OnCreated() // Implemented
    {
        Console.WriteLine("Employee created.");
    }

    public void Create()
    {
        OnCreated();
    }
}

Usage:

Employee emp = new Employee();
emp.Create(); // Output: Employee created.

Benefits

  1. Decoupling: Separates declarations from implementations.

  2. Extensibility: Useful for generated code customization.

  3. Compiler Optimization: No performance cost for unimplemented methods.


Partial methods are a great feature for writing flexible, maintainable, and extensible code, especially in collaborative or tool-generated environments.

Extension Methods in C

Extension methods allow you to add new methods to existing types without modifying their source code or creating derived types. These are static methods that act as if they were instance methods on the extended type.


Key Points

  1. Defined in static classes.

  2. The first parameter specifies the type to extend, prefixed with the this keyword.

  3. Commonly used to extend framework or library classes.


public static class StringExtensions
{
    public static bool IsPalindrome(this string str)
    {
        if (string.IsNullOrEmpty(str)) return false;
        return str.SequenceEqual(str.Reverse());
    }
}

class Program
{
    static void Main()
    {
        string word = "madam";
        Console.WriteLine(word.IsPalindrome()); // Output: True
    }
}

Local Methods in C

Local methods are methods declared inside another method. They are useful for organizing code and are accessible only within the containing method.


Key Points

  1. Declared inside another method.

  2. Can capture and use variables from the enclosing method.

  3. Provide better readability and encapsulation.


class Program
{
    static void Main()
    {
        int Factorial(int number) // Local method
        {
            if (number <= 1) return 1;
            return number * Factorial(number - 1);
        }

        Console.WriteLine(Factorial(5)); // Output: 120
    }
}

Comparison

FeatureExtension MethodsLocal Methods
ScopeAvailable globally to the extended typeLimited to the enclosing method
UsageAdds functionality to existing typesEncapsulates logic within a method
Declared InStatic classesAny method

Both extension and local methods improve code readability and modularity, offering flexibility for different scenarios.

Delegates

A delegate is a type-safe function pointer in C#. It encapsulates references to methods and allows methods to be passed as parameters, stored as variables, or invoked dynamically.

Key Features

  • Type-safe: Ensures that the method signature matches the delegate signature.

  • Multicast: Can invoke multiple methods if combined.

Declaration and Usage

  1. Define a Delegate:

     delegate void MyDelegate(string message);
    
  2. Assign a Method to the Delegate:

     void SayHello(string message) => Console.WriteLine($"Hello, {message}!");
     MyDelegate del = SayHello;
     del("World"); // Output: Hello, World!
    
  3. Multicast Delegate:

     void SayGoodbye(string message) => Console.WriteLine($"Goodbye, {message}!");
     del += SayGoodbye;
     del("World");
     // Output:
     // Hello, World!
     // Goodbye, World!
    

Why Use Delegates?

  • Decouple methods from execution logic.

  • Enable callbacks and event-driven programming.


Predicates

A Predicate is a pre-defined delegate type in C# used specifically for methods that take one parameter and return a boolean (bool). It's often used for testing conditions.

Signature

public delegate bool Predicate<T>(T obj);

Practical Example

using System;
using System.Collections.Generic;

class Program
{
    static bool IsEven(int number) => number % 2 == 0;

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

        // Use Predicate with FindAll
        Predicate<int> predicate = IsEven;
        List<int> evenNumbers = numbers.FindAll(predicate);

        Console.WriteLine(string.Join(", ", evenNumbers)); // Output: 2, 4
    }
}

Action

Action is a pre-defined delegate type that represents a method that does not return a value. It can take up to 16 parameters.

Signature

public delegate void Action<T1, T2, ...>();

Example

using System;

class Program
{
    static void PrintMessage(string message) => Console.WriteLine(message);

    static void Main()
    {
        Action<string> action = PrintMessage;
        action("Hello, Action!"); // Output: Hello, Action!
    }
}

Common Use Case

Use Action when performing operations like logging, printing, or modifying objects without returning a result.


Func

Func is a pre-defined delegate type that represents a method with a return value. It can take up to 16 input parameters, and the last type parameter specifies the return type.

Signature

public delegate TResult Func<T1, T2, ..., TResult>();

Example

using System;

class Program
{
    static int Add(int x, int y) => x + y;

    static void Main()
    {
        Func<int, int, int> func = Add;
        int result = func(3, 4);
        Console.WriteLine(result); // Output: 7
    }
}

Detailed Comparison

FeaturePurposeParametersReturn Type
DelegateGeneral method referenceCustomizableCustomizable
PredicateTesting conditionsSingle (T)bool
ActionPerform operations without return0 to 16void
FuncPerform operations with return0 to 16Customizable

Deep Dive Examples

Using Delegates for Callbacks

using System;

delegate void ProcessData(int value);

class Program
{
    static void PrintDouble(int value) => Console.WriteLine(value * 2);
    static void PrintSquare(int value) => Console.WriteLine(value * value);

    static void Main()
    {
        ProcessData process = PrintDouble;
        process += PrintSquare;

        Console.WriteLine("Processing 5:");
        process(5);
        // Output:
        // 10
        // 25
    }
}

Using Action with Lambda Expressions

using System;

class Program
{
    static void Main()
    {
        Action<string> greet = name => Console.WriteLine($"Hi, {name}!");
        greet("Alice"); // Output: Hi, Alice!
    }
}

Using Func to Chain Operations

using System;

class Program
{
    static void Main()
    {
        Func<int, int, int> multiply = (x, y) => x * y;
        Func<int, int> addTen = result => result + 10;

        int result = multiply(3, 4); // 12
        result = addTen(result);    // 22

        Console.WriteLine(result);  // Output: 22
    }
}

Using Predicate with Anonymous Methods

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
        Predicate<int> isOdd = num => num % 2 != 0;

        List<int> oddNumbers = numbers.FindAll(isOdd);
        Console.WriteLine(string.Join(", ", oddNumbers)); // Output: 1, 3, 5
    }
}

Key Points to Remember

  1. Delegates are powerful for callbacks, event handling, and decoupling code.

  2. Predicate is ideal for conditions, especially with LINQ and collections.

  3. Action is useful for operations without a return value.

  4. Func is versatile for operations that return a value.