Executando verificação de segurança...
2

🤝 Explorando a Evolução dos Delegates no C#: Da Versão 1.0 à Atualidade ⏳

Delegates em C# são estruturas poderosas que permitem tratar métodos como variáveis, armazenando referências para métodos com assinaturas específicas. Assim como int armazena números e string armazena texto, um delegate armazena um bloco de código que pode ser invocado sempre que necessário. Ao longo das versões do C#, novos recursos relacionados a delegates foram introduzidos, cada um trazendo melhorias significativas.

Vamos ver como esta funcionalidade evoluiu através das versões da linguagem:

C# 1.0 - Delegates

Os delegates foram introduzidos no C# 1.0 como uma maneira de encapsular métodos. Eles permitem que métodos sejam armazenados como variáveis e passados como parâmetros.

Exemplo Básico de Delegate:

public delegate void NotifyDelegate(string message);

public class Notifier
{
    public static void SendEmail(string message)
    {
        Console.WriteLine("Email sent: " + message);
    }

    public static void ExecuteNotification(NotifyDelegate notify, string message)
    {
        notify(message);
    }

    static void Main()
    {
        ExecuteNotification(SendEmail, "You have a new message!");
    }
}

Os delegates também suportam múltiplas referências, criando os chamados Multicast Delegates:

NotifyDelegate notifier = SendEmail;
notifier += SendSMS;

notifier("You have a new notification!");

public static void SendSMS(string message)
{
    Console.WriteLine("SMS sent: " + message);
}

C# 2.0 - Eventos e Delegates Anônimos

No C# 2.0, os eventos e os delegates anônimos foram introduzidos. Eventos encapsulam delegates e garantem que apenas a classe que os declara pode invocá-los. Já os delegates anônimos permitem criar funções inline sem a necessidade de métodos nomeados.

Exemplo de Evento:

using System;

public class DownloadEventArgs : EventArgs
{
    public string File { get; set; }
}

public class DownloadManager
{
    public event EventHandler<DownloadEventArgs> DownloadCompleted;

    public void DownloadFile(string file)
    {
        Console.WriteLine($"Downloading {file}...");
        System.Threading.Thread.Sleep(1000);
        OnDownloadCompleted(file);
    }

    protected virtual void OnDownloadCompleted(string file)
    {
        DownloadCompleted?.Invoke(this, new DownloadEventArgs { File = file });
    }
}

class Program {

    static void Main() {
        var manager = new DownloadManager();
        manager.DownloadCompleted += manager_DownloadCompleted;

        void manager_DownloadCompleted(object? sender, DownloadEventArgs e)
        {
            Console.WriteLine($"{e.File} dowload completed");
        }

        manager.DownloadFile("test.png");
    }
}

Exemplo de Delegate Anônimo:

NotifyDelegate notifier = delegate(string message)
{
    Console.WriteLine("Anonymous: " + message);
};
notifier("Hello from Anonymous Delegate!");

C# 3.0 - Expressões Lambda, Func, Action e Predicate

O C# 3.0 trouxe Expressões Lambda e os delegates genéricos pré-definidos Func, Action e Predicate, que simplificaram ainda mais o uso de delegates.

Expressões Lambda:

NotifyDelegate notifier = message => Console.WriteLine("Lambda: " + message);
notifier("Hello from Lambda!");

Func, Action e Predicate:

  • Func: Para métodos que retornam valores onde o ultimo tipo é o tipo de retorno e os demais são os tipos dos parâmetros de entrada.
  • Action: Para métodos que não retornam valores.
  • Predicate: Para métodos que retornam booleanos.

Exemplo:

Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(5, 3));

Action<string> log = message => Console.WriteLine("Log: " + message);
log("Log entry");

Predicate<int> isGreaterThanTen = number => number > 10;
Console.WriteLine(isGreaterThanTen(15));

Árvores de Expressão (Expression Trees)

A árvore de expressão é uma representação de um código em formato de árvore. Elas são muito usadas pelo LINQ e permitem manipulação e avaliação dinâmica de expressões em tempo de execução.

Exemplo Prático com Filtro Dinâmico:

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

class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

class Program
{
    static void Main()
    {
        List<Product> products = new List<Product>
        {
            new Product { Name = "Product A", Price = 10 },
            new Product { Name = "Product B", Price = 20 },
            new Product { Name = "Product C", Price = 30 }
        };

        ParameterExpression param = Expression.Parameter(typeof(Product), "product");
        Expression property = Expression.Property(param, "Price");
        Expression constant = Expression.Constant(20M);
        Expression filter = Expression.GreaterThan(property, constant);

        var lambda = Expression.Lambda<Func<Product, bool>>(filter, param);
        var func = lambda.Compile();

        var results = products.Where(func);
        foreach (var product in results)
        {
            Console.WriteLine(product.Name);
        }
    }
}

C# 6.0 - Expressões de Membros

As Expressões de Membros foram introduzidas no C# 6.0 para simplificar a definição de métodos e propriedades com retornos simples. Em vez de usar blocos de código completos, você pode definir o comportamento diretamente após a seta => reduzindo o uso de blocos {} para métodos simples.

Exemplo Básico de Expressões de Membros:

public class MathHelper
{
    public int Square(int x) => x * x;
    public string Greet(string name) => $"Hello, {name}!";
}

C# 7.0 - Funções Locais

Funções Locais

As Funções Locais permitem definir métodos dentro de outros métodos. Isso ajuda a encapsular lógica que não precisa ser exposta fora do escopo atual.

Exemplo de Função Local:

class Program
{
    static void Main()
    {
        void DisplaySquare(int number)
        {
            int Square(int x) => x * x;
            Console.WriteLine($"Square of {number} is {Square(number)}");
        }

        DisplaySquare(5);
    }
}

C# 9.0 - Lambdas Estáticas

No C# 9.0, foi introduzida a capacidade de definir Lambdas Estáticas. Lambdas estáticas não capturam variáveis do escopo externo, o que pode melhorar o desempenho e reduzir o consumo de memória. Quando você usa o modificador static em uma expressão lambda, você impede que ela acesse variáveis locais ou membros da instância do escopo externo. Isso torna a lambda mais leve, pois não precisa criar uma referência ao contexto externo.

Exemplo de Lambda Estática:

Func<int, int> square = static x => x * x;
Console.WriteLine(square(5));

No exemplo acima, a lambda static x => x * x não pode acessar nenhuma variável do escopo externo. Isso evita capturas desnecessárias e melhora o desempenho.


C# 10.0 - Inferência de Tipo e Atributos em Lambdas

O C# 10.0 trouxe melhorias significativas para expressões lambda, incluindo Inferência de Tipo de Retorno e Suporte a Atributos.

Inferência de Tipo em Lambdas

Antes do C# 10.0, lambdas precisavam ter seu tipo de retorno inferido com base no delegate ou na interface Func/Action/Predicate. No C# 10.0, o compilador pode inferir o tipo de retorno diretamente.

Exemplo de Inferência de Tipo:

var square = (int x) => x * x;
Console.WriteLine(square(4));

O compilador infere que square é do tipo Func<int, int>.

Atributos em Lambdas

Agora é possível adicionar atributos diretamente às expressões lambda.

Exemplo com Atributo:

var lambda = [Obsolete("Use another method")] (int x) => x * x;
Console.WriteLine(lambda(4));

Isso permite que você forneça metadados adicionais às lambdas, como advertências de obsolescência, validações ou descrições.


Conclusão

Delegates evoluíram muito desde sua introdução no C# 1.0. Com a chegada de lambdas, árvores de expressão, funções locais, atributos e inferência de tipos, eles se tornaram uma ferramenta incrivelmente flexível e poderosa. Cada nova versão do C# trouxe melhorias que tornam o código mais legível, eficiente e expressivo.

Ao dominar delegates, lambdas e suas variações modernas, você estará preparado para enfrentar desafios complexos e criar soluções elegantes no ecossistema .NET.

Pratique os exemplos apresentados, explore diferentes cenários e continue aprendendo!

Carregando publicação patrocinada...
1

Não gosto muito do termo Expressões de Membros, mas enfim não é fácil traduzir o que ele é. Ele tem zero ligação com delegates, ele é um método absolutamente comum, só muda a sintaxe quando ele é muito simples. Claro que todo método pode ser usado em um delegate, mas ele não é um na forma como eles são definidos na linguagem.

Idem para funções locais, zero relação com delegates.

As tais "lambdas estáticas também não são muito delegates, e esse é ate o propósito do uso do modificador. Aqui é um pouco mais complicado porque olhando por cima ele seria um delegate, mas internamente ele não é. Na verdade alguns casos, onde a captura não acontece, já tem uma otimização que transforma aquele método em estático e melhora a performance. O modificador static apenas garante que isso acontecerá, porque impedirá uma captura.

Uma IA não consegue entender essas nuances e detalhes. É verdade que alguns seres humanos também, e embora a IA tenha mais chance de "lembrar" dessa informação, ela não costuma ter essa capacidade e vai custar muito caro, e é muito fácil ela não se importar com isso, até porque o grosso no material não fala disso ou fala de forma distante demais para ela associar. O humano só fará essa associação se ele viu pelo menos uma vez isso e/ou tem um entendimento amplo da computação e se questiona: "será que não dá para otimizar isso já que eu sei que um delegate é caro demais e neste caso eu aprendi que que não faz o que gera esse custo"; e aí ele vai estudar mais e achar a informação correta, talvez até com experimentos e raciocínios mais complexos, a IA não faz isso. O humano pode saber quem ele pode confiar mais porque é o criador da especificação ou compilador, a IA não sabe isso ou não consegue associar na hora que precisa, ela não consegue incluir isso na estatística dela a não ser que o humano mande fazer, e mesmo assim tem chance maior dela falhar porque já começa envolver assuntos diferentes, onde ela tende a se perder e talvez dar um resultado ainda pior. O humano também pode errar, de forma diferente. Mas incrível, um dos grandes defeitos do humano é ser teimoso, a IA veio para entregar sem esse defeito, #SQN.

ChatGPT, é você?

Estou virando o paladino "anti" IA (mal usada).

S2


Farei algo que muitos pedem para aprender a programar corretamente, gratuitamente (não vendo nada, é retribuição na minha aposentadoria) (links aqui no perfil também).

1

Poxa, da bastante trabalho escrever os artigos maniero, eu tentei trazer features relacionadas com o tema. Concordo que funções locais não estão diretamente relacionadas com delegates e fugiu um pouco do título do artigo. Na versão inicial do artigo, eu não ia falar só de delegates mas no fim resolvi cortar alguns tópicos porque ficou muito extenso e o título acabou não refletindo exatamente o conteúdo.
Espero que pelo menos o conteúdo ajude àqueles que ainda estão iniciando e tem dificuldades pra entender este assunto assim como eu tive no meu início de carreira.

Abraço