C#: Delegados e eventos

1. Conceitos de delegação

Um delegado é um ponteiro de função com segurança de tipos em C# que permite que métodos sejam passados como parâmetros. Um delegado define a assinatura de um método (tipo de retorno e lista de parâmetros), e somente métodos que correspondam a essa assinatura podem ser atribuídos a uma instância de delegado.

Ao contrário dos ponteiros de função em C/C++, os delegados são orientados a objetos, seguros quanto ao tipo e suportam invocação multicast.


2. Declaração e uso de delegados

Use a palavra-chave delegate para declarar um tipo de delegado; em seguida, crie uma instância e associe-a a um método.

▶ Exemplo

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));
    }
}
▶ Experimente
TEXT 📖 Somente leitura
7
12

3. Delegados de multicast

Os delegados suportam multicast, o que significa que uma única instância de delegado pode fazer referência a vários métodos. Use += para adicionar um método e -= para remover um. Quando invocados, todos os métodos são executados na ordem em que foram adicionados. Para delegados com valores de retorno, uma invocação multicast retorna apenas o resultado do último método.

▶ Exemplo

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");
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Console: System started
File: System started
Database: System started
---
Console: System shutdown
Database: System shutdown

4. Delegados embutidos

O C# oferece três delegados embutidos comumente utilizados que atendem à maioria dos cenários sem a necessidade de definições personalizadas.

(1) Ação

Action representa um método sem valor de retorno. Ele aceita de 0 a 16 parâmetros genéricos: Action, Action<T>, Action<T1,T2>, etc.

▶ Exemplo

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

        Action noParam = () => Console.WriteLine("Parameterless Action");
        noParam();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Hello, Alice!
Parameterless Action

(2) Func

Func<T, TResult> representa um método com um valor de retorno. O último parâmetro genérico é o tipo de retorno, e os anteriores são parâmetros de entrada: Func<TResult>, Func<T, TResult>, Func<T1,T2,TResult>, etc.

▶ Exemplo

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());
    }
}
▶ Experimente
TEXT 📖 Somente leitura
30
14:30:00

(3) Predicado

Predicate<T> representa um método que retorna bool, comumente utilizado para filtragem e verificações condicionais.

▶ Exemplo

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));
    }
}
▶ Experimente
TEXT 📖 Somente leitura
True
False
2, 4, 6

5. Métodos anônimos

Os métodos anônimos utilizam a palavra-chave delegate para definir um bloco de código inline sem a necessidade de um método nomeado separado.

▶ Exemplo

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");
    }
}
▶ Experimente
TEXT 📖 Somente leitura
25
[LOG] Anonymous method example
💡 Os métodos anônimos são os antecessores das expressões lambda. No desenvolvimento moderno em C#, prefere-se o uso de expressões lambda x => x * x devido à sua sintaxe mais concisa.


6. Eventos

Um evento é um mecanismo de publicação-assinatura baseado em delegados. Quando um campo delegado é modificado com a palavra-chave event, somente a classe que declara o evento pode acioná-lo internamente; classes externas podem apenas se inscrever ou cancelar a inscrição usando += e -=, e não podem invocá-lo diretamente.

▶ Exemplo

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();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Event received: Button clicked
Event received: Button clicked
⚠️ Se não houver assinantes, chamar OnClick.Invoke() diretamente resultará em uma exceção NullReferenceException; portanto, você deve usar a invocação condicional nula ?.Invoke().


7. Padrão de publicação-assinatura

O padrão de publicação-assinatura (Editor-Assinante) é o principal cenário de aplicação para eventos. O editor é responsável por gerar eventos, e os assinantes são responsáveis por responder a eles — os dois estão completamente desacoplados.

▶ Exemplo

CSHARP 📖 Somente leitura
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%");
    }
}
43 linhas de lógica (limite de 40, somente leitura)
TEXT 📖 Somente leitura
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%

8. EventArgs personalizados

O T em EventHandler<T> normalmente herda de EventArgs e é usado para transportar dados de eventos personalizados.

▶ Exemplo

CSHARP 📖 Somente leitura
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);
    }
}
48 linhas de lógica (limite de 40, somente leitura)
TEXT 📖 Somente leitura
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)

9. Delegados x Eventos

Recurso Delegado Evento
Natureza Tipo (ponteiro de função) Modificador (instância de delegado restrita)
Chamada externa Pode ser chamada em qualquer lugar Somente a classe que a declara pode levantá-la internamente
Atribuição externa Pode ser substituída por = Só é possível usar += / -=
Utilização Callbacks, padrão de estratégia Publicação-assinatura, mecanismos de notificação
Segurança Inferior Superior, engloba as permissões de invocação
📌 Um evento é, essencialmente, um delegado ao qual foi aplicado o modificador event, que restringe o acesso externo à invocação e à atribuição, garantindo que apenas o emissor possa acionar o evento.

❓ Perguntas Frequentes

P: Qual é a diferença entre um delegado e um evento? R: Um evento é um delegado ao qual foi aplicado o modificador event; o código externo só pode se inscrever ou cancelar a inscrição usando +=/-=, não pode invocá-lo ou sobrescrevê-lo diretamente, e somente a classe declarante pode declará-lo internamente.

P: O que um delegado multicast com valor de retorno retorna? R: Ele retorna o valor de retorno do último método invocado; os valores de retorno dos métodos anteriores são descartados.

P: Qual é a diferença entre Action e Func? R: Action não tem valor de retorno (void), enquanto Func tem um valor de retorno — o último parâmetro genérico é o tipo de retorno.

P: Por que usar ?.Invoke() para disparar um evento? R: Para evitar um NullReferenceException quando o evento não tem assinantes; ?.Invoke() ignora automaticamente a chamada quando o evento é nulo.

P: Predicate e Func<T, bool> são equivalentes? R: Eles são funcionalmente equivalentes. Predicate<T> é uma versão especializada de Func<T, bool> com semântica mais explícita, comumente utilizada em cenários de filtragem.

📖 Resumo

📝 Exercícios

  1. Defina um delegado delegate string Transform(string s);, associe-o a três métodos para converter para maiúsculas, converter para minúsculas e inverter a sequência de caracteres; em seguida, teste a invocação por multicast
  2. Use Func<int, int, bool> para implementar um comparador que determine se o primeiro número é maior que o segundo
  3. Crie uma classe FileWatcher com event EventHandler``<string>`` OnFileChanged, simulando notificações aos assinantes quando um arquivo for alterado
  4. Definir o parâmetro personalizado StockPriceEventArgs (que contém o código da ação, o preço e a porcentagem de variação) e implementar um monitor de cotação de ações que acione um evento quando a porcentagem de variação exceder um limite
  5. Compare o uso de um delegado personalizado com o uso de EventHandler<T> para implementar o mesmo evento e resuma os cenários em que cada um deles se aplica
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%