Qual a diferença entre uma expressão lambda, um closure e um delegate?

Uma expressão lambda, um closure e um delegate são conceitos relacionados, mas têm diferenças sutis entre si.

Uma expressão lambda é uma função anônima que pode ser tratada como um valor em uma linguagem de programação. Ela permite escrever funções de forma concisa e expressiva, sem a necessidade de definir explicitamente um nome. As expressões lambda são frequentemente usadas em linguagens como Python, Java, C# e JavaScript para passar comportamentos como argumentos para outras funções.

Por exemplo, em Python, podemos escrever uma expressão lambda para definir uma função que retorna o quadrado de um número:

squared = lambda x: x ** 2
print(squared(5)) # Output: 25

Um closure é uma função que “captura” variáveis do escopo em que foi definido, permitindo acessá-las mesmo quando a função é chamada fora desse escopo. O closure é uma combinação de uma função e do ambiente em que ela foi criada.

Vamos ver um exemplo em JavaScript:

function outer() {
  var x = 10;

  function inner() {
    console.log(x); // A função inner tem acesso à variável x, mesmo depois do encerramento da função outer.
  }

  return inner;
}

var closureExample = outer();
closureExample(); // Output: 10

Nesse exemplo, a função inner é um closure porque captura a variável x do escopo da função outer e pode acessá-la mesmo quando chamada fora desse escopo.

Por fim, um delegate é um tipo especial de objeto usado em linguagens de programação orientadas a eventos e orientadas a objetos. Ele encapsula uma referência a um método e permite passar esse método como um argumento para outras funções.

Em C#, por exemplo, podemos usar delegates para implementar um callback em um evento:

class Button {
    public delegate void ClickHandler();

    public event ClickHandler OnClick;

    public void Click() {
        // ...

        // Notifica os observers (methods) registrados no evento OnClick
        OnClick?.Invoke();

        // ...
    }
}

class Program {
    static void Main(string[] args) {
        Button button = new Button();

        // Registra um método como observador (callback) no evento OnClick
        button.OnClick += HandleClick;

        // ...

        button.Click(); // Chamada do método Click() na classe Button irá invocar todos os métodos registrados no evento OnClick

        // ...
    }

    static void HandleClick() {
        Console.WriteLine("Button clicked!");
    }
}

Neste exemplo em C#, o delegate ClickHandler é usado para definir a assinatura do método que será chamado quando o evento OnClick ocorrer. O método Main registra o método HandleClick como um observador do evento OnClick e, quando o botão é clicado (chamada do método Click), todos os métodos registrados no evento são invocados.

Resumindo, uma expressão lambda é uma função anônima, um closure é uma função que captura variáveis de um escopo e um delegate é um tipo de objeto que permite passar métodos como argumentos.