C#

C# Fundamentals

19 lessons

Progress0%
1. Introduction to C#
1What is C#?
2. Variables and Data Types
1Data Types in C#
3. Control Flow
ConditionalsLoops
4. Methods
Defining MethodsOptional Parameters and Overloading
5. Object-Oriented Programming
Classes and PropertiesInheritanceInterfaces and Generics
6. LINQ and Async
LINQ Queriesasync/await
7. Exception Handling
try/catch/finally & Exception TypesCustom Exceptions & IDisposable
8. Delegates & Events
Delegates & LambdaEvents & Event Handlers
9. Records & Pattern Matching
Record TypesPattern Matching & Switch Expressions
10. File I/O & JSON
File & Stream OperationsJSON Serialization
All Tutorials
C#Delegates & Events
Lesson 14 of 19 min
Chapter 8 · Lesson 1

Delegates & Lambda

A delegate is a type-safe function pointer — an object that holds a reference to one or more methods matching a specific signature. Delegates are the foundation of events, callbacks, and higher-order functions in C#.

Declaring and Using Delegates

You declare a delegate type with the delegate keyword, specifying the return type and parameter list. Any method (static or instance) whose signature matches can be assigned to a variable of that delegate type.

code
public delegate int Transformer(int input);

Transformer square = x => x * x;
Console.WriteLine(square(5)); // 25

Multicast Delegates

A single delegate variable can hold references to multiple methods. Use += to attach and -= to detach. When invoked, all attached methods run in the order they were added. The return value of a multicast delegate is the return value of the last method in the chain (earlier return values are discarded), so multicast is most common with void-returning delegates.

code
Action log = () => Console.WriteLine("Logger 1");
log += () => Console.WriteLine("Logger 2");
log(); // both run

Built-In Generic Delegates

Instead of declaring custom delegate types, use the three generic families provided by the BCL:

TypeSignatureUse case
Action<T1,...>void returnSide-effect callbacks
Func<T1,...,TResult>non-void returnTransformations, projections
Predicate<T>bool returnFiltering, conditions

Lambda Expressions

Lambdas are the most concise way to create delegate instances. An expression lambda (x => x * x) has an implicit return. A statement lambda (x => { ... return ...; }) allows multiple statements. Lambdas can capture variables from the enclosing scope (closures).

Anonymous Methods vs Lambdas

Anonymous methods (delegate(int x) { return x * x; }) are the older syntax introduced in C# 2. Lambdas (C# 3+) are shorter and support expression trees. Prefer lambdas in all new code.

Delegate.Combine

Under the hood, += calls Delegate.Combine, which creates a new multicast delegate. -= calls Delegate.Remove. Both return a new delegate object — delegates are immutable.

Code Examples

Multicast Delegate Examplecsharp
using System;

// Custom delegate type
public delegate void Notifier(string message);

class Program
{
    static void LogToConsole(string msg) =>
        Console.WriteLine(````````[Console] ${msg}````````);

    static void LogToFile(string msg) =>
        Console.WriteLine(````````[File]    ${msg}````````); // simulated

    static void Main()
    {
        Notifier notifier = LogToConsole;
        notifier += LogToFile; // attach second method

        Console.WriteLine("--- Invoking multicast delegate ---");
        notifier("System started");

        Console.WriteLine();
        notifier -= LogToFile; // detach

        Console.WriteLine("--- After removing LogToFile ---");
        notifier("System ready");

        // GetInvocationList shows attached methods
        Console.WriteLine(````````
Invocation list count: ${notifier.GetInvocationList().Length}````````);
    }
}

+= adds methods to the delegate's invocation list. -= removes them. GetInvocationList() lets you inspect all currently attached methods.

Action / Func with Lambda Expressionscsharp
using System;
using System.Collections.Generic;

// Action<T> — void return, useful for side effects
Action<string> print = msg => Console.WriteLine(````````> ${msg}````````);

// Func<TInput, TOutput> — returns a value
Func<int, int> square  = x => x * x;
Func<int, int> doubled = x => x * 2;

// Compose two Func delegates
Func<int, int> squareThenDouble = x => doubled(square(x));

print(````````square(4)          = ${square(4)}````````);
print(````````doubled(4)         = ${doubled(4)}````````);
print(````````squareThenDouble(4) = ${squareThenDouble(4)}````````);

// Func used as a higher-order parameter
static List<T> Transform<T>(List<T> list, Func<T, T> transform)
{
    var result = new List<T>();
    foreach (var item in list)
        result.Add(transform(item));
    return result;
}

var nums = new List<int> { 1, 2, 3, 4, 5 };
var squared = Transform(nums, x => x * x);

Console.WriteLine(````````Squared list: ${string.Join(", ", squared)}````````);

Action<T> and Func<T,TResult> are built-in generic delegates that eliminate the need to declare custom types. Lambda expressions provide a concise syntax for creating delegate instances inline.

Predicate and Higher-Order Functionscsharp
using System;
using System.Collections.Generic;

// Predicate<T> — returns bool, used for filtering
Predicate<int> isEven    = n => n % 2 == 0;
Predicate<int> isPositive = n => n > 0;

// Combine predicates: both must be true
Predicate<int> isEvenAndPositive = n => isEven(n) && isPositive(n);

// A generic filter method using Predicate<T>
static List<T> Filter<T>(IEnumerable<T> source, Predicate<T> predicate)
{
    var result = new List<T>();
    foreach (var item in source)
        if (predicate(item))
            result.Add(item);
    return result;
}

var numbers = new List<int> { -4, -1, 0, 1, 2, 3, 4, 5, 6 };

var evens            = Filter(numbers, isEven);
var positiveEvens    = Filter(numbers, isEvenAndPositive);
var bigOdds          = Filter(numbers, n => n > 2 && n % 2 != 0);

Console.WriteLine(````````Evens          : ${string.Join(", ", evens)}````````);
Console.WriteLine(````````Positive evens : ${string.Join(", ", positiveEvens)}````````);
Console.WriteLine(````````Big odd nums   : ${string.Join(", ", bigOdds)}````````);

Predicate<T> is equivalent to Func<T, bool> and is used throughout the BCL for filtering. Composing predicates with logical operators lets you build complex conditions from simple ones.

Quick Quiz

1. What happens when you invoke a multicast delegate that has a non-void return type?

2. Which built-in generic delegate type should you use for a method that takes two ints and returns a bool?

3. What does the += operator do when applied to a delegate variable?

Was this lesson helpful?

PreviousNext