404 Not Found

404 Not Found


nginx

Delegates and Events

Delegate Concepts

A delegate is a type-safe function pointer in C# that allows methods to be passed as parameters. A delegate defines a method's signature (return type and parameter list), and only methods matching that signature can be assigned to a delegate instance.

Unlike C/C++ function pointers, delegates are object-oriented, type-safe, and support multicast invocation.

Declaring and Using Delegates

Use the delegate keyword to declare a delegate type, then create an instance and associate it with a method.

Example

CSHARP
delegate int MathOp(int a, int b);

class Program
{
    static int Add(int a, int b) => a + b;
    static int Multiply(int a, int b) => a * b;

    static void Main()
    {
        MathOp op = Add;
        Console.WriteLine(op(3, 4));

        op = Multiply;
        Console.WriteLine(op(3, 4));
    }
}
▶ Try it Yourself
TEXT
7
12

Multicast Delegates

Delegates support multicast, meaning a single delegate instance can reference multiple methods. Use += to add a method and -= to remove one. When invoked, all methods are executed in the order they were added. For delegates with return values, a multicast invocation only returns the result of the last method.

Example

CSHARP
delegate void NotifyHandler(string msg);

class Program
{
    static void LogToConsole(string msg) => Console.WriteLine($"Console: {msg}");
    static void LogToFile(string msg) => Console.WriteLine($"File: {msg}");
    static void LogToDb(string msg) => Console.WriteLine($"Database: {msg}");

    static void Main()
    {
        NotifyHandler handler = LogToConsole;
        handler += LogToFile;
        handler += LogToDb;

        handler("System started");

        Console.WriteLine("---");

        handler -= LogToFile;
        handler("System shutdown");
    }
}
▶ Try it Yourself
TEXT
Console: System started
File: System started
Database: System started
---
Console: System shutdown
Database: System shutdown

Built-in Delegates

C# provides three commonly used built-in delegates that cover most scenarios without requiring custom definitions.

Action

Action represents a method with no return value. It supports 0 to 16 generic parameters: Action, Action<T>, Action<T1,T2>, etc.

Example

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

        Action noParam = () => Console.WriteLine("Parameterless Action");
        noParam();
    }
}
▶ Try it Yourself
TEXT
Hello, Alice!
Parameterless Action

Func

Func<T, TResult> represents a method with a return value. The last generic parameter is the return type, and the preceding ones are input parameters: Func<TResult>, Func<T, TResult>, Func<T1,T2,TResult>, etc.

Example

CSHARP
class Program
{
    static void Main()
    {
        Func<int, int, int> add = (a, b) => a + b;
        Console.WriteLine(add(10, 20));

        Func<string> getTime = () => DateTime.Now.ToString("HH:mm:ss");
        Console.WriteLine(getTime());
    }
}
▶ Try it Yourself
TEXT
30
14:30:00

Predicate

Predicate<T> represents a method that returns bool, commonly used for filtering and conditional checks.

Example

CSHARP
class Program
{
    static void Main()
    {
        Predicate<int> isEven = n => n % 2 == 0;
        Console.WriteLine(isEven(4));
        Console.WriteLine(isEven(7));

        var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
        var evens = numbers.FindAll(isEven);
        Console.WriteLine(string.Join(", ", evens));
    }
}
▶ Try it Yourself
TEXT
True
False
2, 4, 6

Anonymous Methods

Anonymous methods use the delegate keyword to define an inline code block without needing a separate named method.

Example

CSHARP
class Program
{
    static void Main()
    {
        Func<int, int> square = delegate(int x) { return x * x; };
        Console.WriteLine(square(5));

        Action<string> log = delegate(string msg) { Console.WriteLine($"[LOG] {msg}"); };
        log("Anonymous method example");
    }
}
▶ Try it Yourself
TEXT
25
[LOG] Anonymous method example
💡 Anonymous methods are the predecessor of lambda expressions. Modern C# development prefers lambda expressions x => x * x for their more concise syntax.

Events

An event is a publish-subscribe mechanism based on delegates. When a delegate field is modified with the event keyword, only the class that declares the event can raise it internally; external classes can only subscribe or unsubscribe using += and -=, and cannot invoke it directly.

Example

CSHARP
class Button
{
    public event EventHandler<string> OnClick;

    public void Click()
    {
        OnClick?.Invoke(this, "Button clicked");
    }
}

class Program
{
    static void Main()
    {
        var btn = new Button();
        btn.OnClick += (sender, msg) => Console.WriteLine($"Event received: {msg}");
        btn.Click();
        btn.Click();
    }
}
▶ Try it Yourself
TEXT
Event received: Button clicked
Event received: Button clicked
⚠️ If there are no subscribers, calling OnClick.Invoke() directly will throw a NullReferenceException, so you should use the null-conditional invocation ?.Invoke().

Publish-Subscribe Pattern

The publish-subscribe pattern (Publisher-Subscriber) is the core application scenario for events. The publisher is responsible for raising events, and the subscribers are responsible for responding to them — the two are completely decoupled.

Example

CSHARP
class NewsPublisher
{
    public event EventHandler<string> OnNewsPublished;

    public void Publish(string news)
    {
        Console.WriteLine($"Publishing news: {news}");
        OnNewsPublished?.Invoke(this, news);
    }
}

class NewsSubscriber
{
    public string Name { get; }

    public NewsSubscriber(string name)
    {
        Name = name;
    }

    public void Subscribe(NewsPublisher publisher)
    {
        publisher.OnNewsPublished += HandleNews;
    }

    public void Unsubscribe(NewsPublisher publisher)
    {
        publisher.OnNewsPublished -= HandleNews;
    }

    private void HandleNews(object sender, string news)
    {
        Console.WriteLine($"  [{Name}] Received: {news}");
    }
}

class Program
{
    static void Main()
    {
        var publisher = new NewsPublisher();
        var sub1 = new NewsSubscriber("Reader A");
        var sub2 = new NewsSubscriber("Reader B");

        sub1.Subscribe(publisher);
        sub2.Subscribe(publisher);

        publisher.Publish("C# 12 officially released");

        sub2.Unsubscribe(publisher);
        publisher.Publish(".NET 9 performance improved by 50%");
    }
}
▶ Try it Yourself
TEXT
Publishing news: C# 12 officially released
  [Reader A] Received: C# 12 officially released
  [Reader B] Received: C# 12 officially released
Publishing news: .NET 9 performance improved by 50%
  [Reader A] Received: .NET 9 performance improved by 50%

Custom EventArgs

The T in EventHandler<T> typically inherits from EventArgs and is used to carry custom event data.

Example

CSHARP
class TemperatureEventArgs : EventArgs
{
    public double Temperature { get; }
    public DateTime Timestamp { get; }

    public TemperatureEventArgs(double temperature)
    {
        Temperature = temperature;
        Timestamp = DateTime.Now;
    }
}

class TemperatureMonitor
{
    public event EventHandler<TemperatureEventArgs> OnTemperatureAlert;
    private double _threshold;

    public TemperatureMonitor(double threshold)
    {
        _threshold = threshold;
    }

    public void CheckTemperature(double current)
    {
        Console.WriteLine($"Current temperature: {current}°C");
        if (current > _threshold)
        {
            var args = new TemperatureEventArgs(current);
            OnTemperatureAlert?.Invoke(this, args);
        }
    }
}

class Cooler
{
    public void StartCooling(object sender, TemperatureEventArgs e)
    {
        Console.WriteLine($"  🚨 Temperature exceeded! {e.Temperature}°C > threshold, starting cooling (time: {e.Timestamp:HH:mm:ss})");
    }
}

class Program
{
    static void Main()
    {
        var monitor = new TemperatureMonitor(30.0);
        var cooler = new Cooler();

        monitor.OnTemperatureAlert += cooler.StartCooling;

        monitor.CheckTemperature(25.0);
        monitor.CheckTemperature(32.5);
        monitor.CheckTemperature(28.0);
        monitor.CheckTemperature(35.0);
    }
}
▶ Try it Yourself
TEXT
Current temperature: 25°C
Current temperature: 32.5°C
  🚨 Temperature exceeded! 32.5°C > threshold, starting cooling (time: 14:30:00)
Current temperature: 28°C
Current temperature: 35°C
  🚨 Temperature exceeded! 35°C > threshold, starting cooling (time: 14:30:01)

Delegates vs Events

Feature Delegate Event
Nature Type (function pointer) Modifier (restricted delegate instance)
External invocation Can be invoked anywhere Only the declaring class can raise it internally
External assignment Can be overwritten with = Can only use += / -=
Usage Callbacks, strategy pattern Publish-subscribe, notification mechanisms
Safety Lower Higher, encapsulates invocation permissions
📌 An event is essentially a delegate with the event modifier applied, which restricts external access to invocation and assignment, ensuring that only the publisher can raise the event.

❓ FAQ

Q What is the difference between a delegate and an event?
A An event is a delegate with the event modifier applied; external code can only subscribe/unsubscribe using +=/-=, cannot invoke or overwrite it directly, and only the declaring class can raise it internally.
Q What does a multicast delegate with a return value return?
A It returns the return value of the last invoked method; the return values of earlier methods are discarded.
Q What is the difference between Action and Func?
A Action has no return value (void), while Func has a return value — the last generic parameter is the return type.
Q Why use ?.Invoke() to raise an event?
A To prevent a NullReferenceException when the event has no subscribers; ?.Invoke() automatically skips the call when the event is null.
Q Are Predicate and Func<T, bool> equivalent?
A They are functionally equivalent. Predicate<T> is a specialized version of Func<T, bool> with more explicit semantics, commonly used in filtering scenarios.

📖 Summary

📝 Exercises

  1. Define a delegate string Transform(string s); delegate, associate it with three methods for converting to uppercase, converting to lowercase, and reversing the string, then test multicast invocation
  2. Use Func<int, int, bool> to implement a comparator that determines whether the first number is greater than the second
  3. Create a FileWatcher class with an event EventHandler<string> OnFileChanged, simulating notifications to subscribers when a file changes
  4. Define custom StockPriceEventArgs (containing stock symbol, price, and change percentage), and implement a stock price monitor that raises an event when the change percentage exceeds a threshold
  5. Compare using a custom delegate versus EventHandler<T> to implement the same event, and summarize the applicable scenarios for each
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏