Explorando o LINQ – Background


Antes de efetivamente aprendermos sobre o LINQ e suas respectivas funcionalidades, não podemos deixar de lado alguns recursos que foram incorporados nas versões 2.0 e 3.5 do .NET Framework que são utilizadas imensamente quando estamos manipulando determinados objetos utilizando o LINQ. Essas funcionalidades fazem parte das versões 2.0 e 3.0 do Visual C# e das versões 8.0 e 9.0 do Visual Basic .NET, lembrando que cada uma dessas versões estão contidas em versões diferentes do .NET Framework.

É extremamente importante que essas funcionalidades fiquem claras pois elas serão utilizadas nas seções posteriores e assumirei isso como pré-requisito, não comentando mais sobre o funcionamento das mesmas. É importante dizer também que algumas destas funcionalidades não será possível abordar totalmente, pois necessitariam de um artigo exclusivo e, nestes casos, farei uma simples introdução e apontarei possíveis fontes de estudos para que possa aprimorar seus conhecimentos nesta determinada funcionalidade. Nesta seção abordaremos Generics, Nullable Types, Actions, Predicates, Comparison, Converter, Inference Types, Object Initializers, Anonymous Types, Extension Methods, Implicitly Typed Arrays e Lambda Expressions.

Generics

Generics é um novo conceito introduzido na versão 2.0 do .NET Framework, como parte integrante do CLS (Common Language Specification). Generics permitem termos classes, métodos, propriedades, delegates e Interfaces que trabalhem com um tipo não especificado. Esse tipo “não especificado” quer dizer que estes membros trabalharão com os tipos que você especificar em sua construção.

Como já sabemos, o ArrayList permite adicionarmos qualquer tipo dentro dele. Mas e se quiséssemos apenas adicionar valores inteiros, ou somente strings? Isso não seria possível pois o método Add aceita um System.Object. Com Generics, é possível criar coleções de um determinado tipo, o que permitirá que o usuário (outro desenvolvedor) somente adicione objetos do mesmo tipo e, se por acaso ele quiser adicionar algo incompatível, o erro já é detectado em design-time. O .NET Framework 2.0 fornece uma porção de coleções que utilizam Generics e que estão contidas dentro do namespace System.Collections.Generic. Classes genéricas oferecem várias vantagens, entre elas:

  • Reusabilidade: Um simples tipo genérico pode ser utilizado em diversos cenários. Um exemplo é uma classe que fornece um método para somar dois números. Só que estes números podem ser do tipo Integer, Double ou Decimal. Utilizando o conceito de Generics, não precisaríamos de overloads do método Somar(…). Bastaria criar um único método com parâmetros genéricos e a especificação do tipo a ser somado fica a cargo do consumidor.

  • Type-safety: Generics fornecem uma melhor segurança, mais especificamente em coleções. Quando criamos uma coleção genérica e especificamos em seu tipo uma string, somente podemos adicionar strings, ao contrário do ArrayList.

  • Performance: Fornecem uma melhor performance, já que não há mais o boxing e unboxing. Além disso, o número de conversões cai drasticamente, já que tudo passa a trabalhar com um tipo especificado, o que evita transformarmos em outro tipo para termos acesso às suas propriedades, métodos e eventos.

Uma classe que aceita um tipo em sua declaração pode trabalhar internamente com este tipo. Isso quer dizer que os métodos podem aceitar em seus parâmetros objetos do tipo especificado na criação da classe, retornar esses tipos em propriedades, etc..

Para exemplificarmos, vejamos uma classe que aceita um valor genérico, o que quer dizer que ela pode trabalhar (fortemente tipada) com qualquer tipo: através do exemplo abaixo “T” que identifica o tipo genérico que já pode ser acessado internamente pela classe.

public class ClasseGenerica<T>
{
    private T _valor;

    public T Valor
    {
        get
        {
            return this._valor;
        }
        set
        {
            this._valor = value;
        }
    }
}

//Utilização:
ClasseGenerica<string> classe1 = new ClasseGenerica<string>();
classe1.Valor = ".NET";

ClasseGenerica<int> classe2 = new ClasseGenerica<int>();
classe2.Valor = 123;

O que diferencia uma classe normal de uma classe genérica é o tipo que devemos especificar durante a criação da mesma. O valor “T” pode ser substituído por qualquer palavra que você achar mais conveniente para a situação e, quando quiser referenciar o tipo genérico em qualquer parte da classe, poderá acessá-lo como um tipo qualquer, como um Integer e felizmente, o Intellisense dá suporte completo a Generics. Digamos que sua classe somente trabalhará com Streams, então poderia definir “T” como “TStream” (se assim desejar):

public class ClasseGenerica<TStream>
{
    //....
}

Como podemos notar no exemplo, a classe1 trabalha somente com valores do tipo string. Já a classe2 somente trabalha com valores do tipo inteiro. Mesmo que quiser adicionar um tipo incompatível, o erro já é informado em design-time. Mas os Generics não param por aqui. Existem muitas outras possibilidades para tornarmos os Generics ainda mais poderosos, como por exemplo critérios (constraints), criação de métodos genéricos, delegates, Interfaces, entre outros e, para uma explicação completa sobre isso, consultem este artigo do MSDN.

Nullable Types

Qualquer tipo-valor em .NET possui sempre um valor padrão. Isso quer dizer que ele nunca poderá ter um valor nulo e mesmo que tentar, definindo nulo (Nothing em VB.NET e null em C#) para o tipo-valor, o compilador atirará uma exceção. Um exemplo é a estrutura DateTime. Ela tem um valor padrão que é 01/01/0001 00:00:00. Apesar de estranha, é uma data válida.

Muitas vezes um tipo pode ter uma data não informada, como por exemplo: imagine um objeto Funcionario que tem uma propriedade chamada DataDemissao. Só que este funcionário não foi demitido. Então como conseguimos distingüir se essa data é válida ou não? Pois bem, o .NET Framework 2.0 fornece o que chamamos de Nullable Types.

System.Nullable<T> é uma estrutura de dados genérica que aceita como tipo qualquer outro tipo, desde que esse tipo seja uma outra estrutura (tipo-valor), como por exemplo: Int32, Double, DateTime, etc.. Através deste tipo especial podemos definir valores nulos para ele, como por exemplo:

Nullable<DateTime> dataDemissao = null;

A estrutura genérica Nullable<T> fornece também uma propriedade chamada HasValue do tipo booleana que retorna um valor indicando se existe ou não um valor definido. E note que a estrutura Nullable<T> trata de uma estrutura genérica, onde o tipo a ser definido obrigatoriamente deve também ser uma estrutura, devido a constraint que obriga isso. Esses tipos são ideais para utilizá-los junto com registros retornados de uma base de dados qualquer. Apesar de não ter uma grande integração, ajuda imensamente para conseguirmos mapear as colunas do result-set para as propriedades dos objetos da aplicação que permitem valores nulos.

Actions

Action trata-se de um delegate que foi introduzido dentro da versão 2.0 do .NET Framework que representa um método que executa uma ação em um objeto específico. Trata-se de um delegate genérico que não possui nenhum retorno. Esse delegate e os delegates que veremos a seguir são comumente utilizandos em conjunto com arrays, evitando a necessidade da criação de um método exclusivo para iterar pela respectiva coleção efetuando alguma tarefa para cada um dos seus itens que estão contidos dentro dela. Se analisarmos a classe Array veremos vários métodos que recebem como parâmetros delegates do tipo Actions, Predicates, Comparison e Converter.

Para exemplificar, utilizamos o método ForEach da classe List que aceita como parâmetro um delegate do tipo Action que deve referenciar o método que será executado para cada um dos itens da coleção. É bom lembrar que o delegate Action trata-se de um delegate genérico e o tipo a especificar neste cenário deve, obrigatoriamente, ser o mesmo tipo que foi especificado na criação do objeto List. Como exemplo criarei também uma classe chamada Usuario que será utilizada pelos exemplos durante esta seção:

public class Usuario
{
    private string _nome;

    public Usuario(string nome)
    {
        this._nome = nome;
    }

    public string Nome
    {
        get
        {
            return this._nome;
        }
        set
        {
            this._nome = value;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        nomes.ForEach(new Action<Usuario>(Write));
    }

    private static void Write(Usuario user)
    {
        Console.WriteLine(user.Nome);
    }
}

O Visual C# ainda permite a utilização do que chamamos de métodos anônimos (in-line). Isso evitará a criação de um método exclusivo para a realização desta tarefa. Se o método será utilizado somente naquele local, então podemos omitir a criação do método através da seguinte sintaxe:

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        nomes.ForEach(new Action<Usuario>(delegate(Usuario user)
        {
            Console.WriteLine(user.Nome);        
        }));
    }
}

Predicates

Os Predicates trabalham de forma semelhante ao Action, com a exceção de que ao invés de ser um void ele retorna um valor booleano. O Predicate representa um método que define alguns critérios, determinando se um objeto atende ou não a estes critérios estabelecidos pelo Predicate. Através do exemplo abaixo, utilizamos o método Find da classe List que, dado um Predicate, ele analisa se o usuário está ou não contido dentro da listagem e, se estiver, a instância deste usuário será atribuído a variável busca.

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        Usuario busca = users.Find(new Predicate<Usuario>(Search));
        if (busca != null)
            Console.WriteLine(busca.Nome);
    }

    private static bool Search(Usuario user)
    {
        return user.Nome == "Anna";
    }
}

O Visual C# ainda permite a utilização do que chamamos de métodos anônimos (in-line). Isso evitará a criação de um método exclusivo para a realização desta tarefa. Se o método será utilizado somente naquele local, então podemos omitir a criação do método através da seguinte sintaxe:

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        Usuario busca = users.Find(new Predicate<Usuario>(delegate(Usuario user)
        {
            return user.Nome == "Anna";        
        }));

        if (busca != null)
            Console.WriteLine(busca.Nome);
    }
}

Comparison

Este delegate representa um método que é utilizado para comparar dois tipos. Este método é geralmente utilizado em conjunto com o método Sort da classe fornecido pelos array que permite ordenar uma coleção de forma ascendente ou descendente. É importante lembrar que o método Sort não retorna nenhum valor, é apenas um void (Sub em VB.NET) que manipula os itens internos ao array. Através do exemplo abaixo, utilizamos um Comparer padrão para ordenar os usuários que estão contidos dentro da coleção:

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        users.Sort(new Comparison<Usuario>(Sort));
        users.ForEach(new Action<Usuario>(Write));
    }

    private static int Sort(Usuario u1, Usuario u2)
    {
        return Comparer<string>.Default.Compare(u1.Nome, u2.Nome);
    }

    private static void Write(Usuario user)
    {
        Console.WriteLine(user.Nome);
    }
}

O Visual C# ainda permite a utilização do que chamamos de métodos anônimos (in-line). Isso evitará a criação de um método exclusivo para a realização desta tarefa. Se o método será utilizado somente naquele local, então podemos omitir a criação do método através da seguinte sintaxe:

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        users.Sort(new Comparison<Usuario>(delegate(Usuario u1, Usuario u2)
        {
            return Comparer<string>.Default.Compare(u1.Nome, u2.Nome);
        }));

        users.ForEach(new Action<Usuario>(delegate(Usuario u)
        {
            Console.WriteLine(u.Nome);
        }));
    }
}

Converter

A classe List possui um método chamado ConvertAll que recebe como parâmetro um delegate do tipo Converter. Este é um delegate genérico que devemos especificar qual será o tipo de dado de entrada e de saída em que o método ConvertAll deverá converter cada um dos elementos contidos dentro da coleção. Esse método retornará um novo objeto List onde cada um dos elementos desta coleção será do tipo especificado na criação do delegate Converter. O exemplo abaixo ilustra esse processo, onde especificamos que a coleção de objetos Usuarios será convertida para uma coleção de strings, contendo apenas o nome de cada um deles.

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        List<string> nomes = 
            users.ConvertAll<string>(new Converter<Usuario, string>(Conversao));

        nomes.ForEach(new Action<string>(Write));
    }

    private static string Conversao(Usuario user)
    {
        return user.Nome;
    }

    private static void Write(string nome)
    {
        Console.WriteLine(nome);
    }
}

O Visual C# ainda permite a utilização do que chamamos de métodos anônimos (in-line). Isso evitará a criação de um método exclusivo para a realização desta tarefa. Se o método será utilizado somente naquele local, então podemos omitir a criação do método através da seguinte sintaxe:

class Program
{
    static void Main(string[] args)
    {
        List<Usuario> users = new List<Usuario>();
        users.Add(new Usuario("Israel"));
        users.Add(new Usuario("Claudia"));
        users.Add(new Usuario("Anna"));
        users.Add(new Usuario("Juliano"));
        users.Add(new Usuario("Leandro"));
        users.Add(new Usuario("Decio"));

        List<string> nomes = users.ConvertAll<string>(
            new Converter<Usuario, string>(delegate(Usuario user)
        {
            return user.Nome;
        }));

        nomes.ForEach(new Action<string>(delegate(string nome)
        {
            Console.WriteLine(nome);
        }));
    }
}

Observação: Quando utilizamos os métodos anônimos do Visual C#, eles apenas são uma forma eficiente e elegante de criarmos o código que será executado através do delegate. Ao compilar a aplicação, os métodos são explicitamente criados dentro do Assembly e são automaticamente vinculados a algum dos delegates que vimos acima.

Inference Types

As inferências de tipo ou tipos implícitos são uma exclusividade da versão 3.5 do .NET Framework. Esta funcionalidade permite ocultarmos a declaração do tipo da variável durante a escrita do código. Mas não pense que isso irá tornar a variável do tipo System.Object (como acontece nas versões anteriores do VB.NET); ao invés disso, o tipo que a variável terá será definido a partir do valor com o qual você a inicializa. Sendo assim, podemos passar a declarar as variáveis da seguinte forma:

var id = 123;
var nomes = new string[] {"Israel", "Claudia", "Juliano"};
var valor = 12.0;

Ao declarar as variáveis desta forma, em design-time você já terá suporte aos membros do tipo que você inicializou a mesma. Para comprovar que o tipo da variável irá depender exclusivamente do valor a ela definida, podemos visualizar o código compilado, que será algo mais ou menos como o que é mostrado logo abaixo:

int id = 123;
string[] nomes = new string[] {"Israel", "Claudia", "Juliano"};
double valor = 12.0;

Entretanto, esse tipo de declaração possui algumas restrições, quais são detalhes na listagem abaixo:

  • Toda variável deve ser inicializada;

  • O inicializador deve ser uma expressão. O inicializador não pode ser um inicializador de objeto (veremos isso detalhadamente mais abaixo) ou de coleção, mas pode ser uma nova expressão que inclui um inicializador de objeto ou coleção;

  • Não é possível inicializar a variável com um tipo nulo;

  • Não é possível inicializar uma variável com ela mesma;

  • Não é possível incluir múltiplas variáveis na mesma declaração.

No caso do Visual C#, além de utilizar a inferência de tipos na declaração das variáveis, podemos fazer o mesmo na inicialização de um laço for ou até mesmo dentro do um bloco using. No primeiro caso, dentro do laço For, a variável é automaticamente inicializada com o mesmo tipo dos elementos da coleção; já no caso do bloco using, a variável terá o mesmo tipo da instância que é a ela atribuída. Só é importante lembrar que, neste caso, o objeto deve obrigatoriamente implementar a Interface IDisposable. O código abaixo ilustra esses dois casos:

//Utilização no For
var nomes = new string[] { "Israel", "Claudia", "Juliano" };
foreach (var nome in nomes)
    Console.WriteLine(nome);

//Utilização no bloco using
using (var t = new Teste())
{
    //...
}

public class Teste : IDisposable
{
    //Implementação...
}

Object Initializers

Os inicializadores de objetos consiste em uma seqüência de membros inicializada, que devem estar entre { } (chaves), onde cada uma das propriedades são separadas por vírgulas. Para que cada propriedade seja inicializada você deve especificar o nome da mesma e, depois do sinal de igual, definir o seu respectivo valor. É importante dizer que você pode inicializar os membros independente de qual tipo ele seja. O exemplo abaixo ilustra como devemos proceder para utilizar esse recurso:

public class Usuario
{
    public int Id;
    public string Nome;
}

//Utilização

Usuario user = new Usuario() { Id = 1, Nome = "Israel" };

Como podemos notar, o Visual Basic .NET exige a utilização de uma keyword chamada With para indicar ao compilador que você está tentando utilizar o inicializador de objeto. Finalmente, é importante dizer que os inicializadores de objetos não tem nenhuma relação com o construtor da classe, ou seja, eles tem funcionalidades diferentes pois, como o próprio nome diz, os inicializadores são úteis para definirmos valores para as propriedades públicas no momento da criação do objeto.

Anonymous Types

Basicamente, os tipos anônimos, assim como os métodos anônimos, são a habilidade que as linguagens Visual Basic .NET e Visual C# fornecem que nos permite criar um tipo, sem explicitamente criar uma classe formal para o mesmo. Esse recurso é extremamente importante na utilização do LINQ, que veremos nas próximos seções deste artigo. O código abaixo mostra a sintaxe de como podemos criar um objeto com duas propriedades e, logo após a sua criação, já podemos acessar as propriedades recém definidas para o tipo anônimo:

var u = new { Nome = "Israel", Idade = 25 };
Console.WriteLine(u.Nome);

Claro que, ao compilar o projeto, automaticamente uma classe com as respectivas propriedades é criada. Os tipos anônimos permitem encurtar o trabalho, sem a necessidade de perdemos tempo para criar uma “classe temporária”. Vale lembrar que veremos a grande utilidade desses recursos quando abordarmos o LINQ.

Extension Methods

Os extension methods nada mais é que métodos estáticos que permitem “adicionarmos” estes métodos a tipos pré-existentes e que podem ser invocados a partir das instâncias desses tipos. Para exemplificar, poderíamos adicionar um método rotineiro ao tipo string e assim utilizarmos por toda a aplicação. Especialmente neste caso, existem algumas diferenças consideráveis para a definição de extension methods nas linguagens VB.NET e Visual C#.

No caso do VB.NET, esses métodos devem obrigatoriamente ser definidos dentro de um módulo decorado com um atributo chamado ExtensionAttribute, que está contido dentro do namespace System.Runtime.CompilerServices (Assembly System.Core.dll). No caso do C#, a exigência é que estes métodos devem ser declarados dentro de uma classe estática, sem nenhum atributo definido. O que vai definir explicitamente o tipo onde este método será “adicionado” é o primeiro parâmetro deste método que está criando. Os parâmetros que vem a seguir são utilizados para informar um parâmetro qualquer que é necessário para que este extension method possa realizar o seu trabalho. O código abaixo ilustra como criá-los:

public static class Helper
{
    public static void Escrever(this string s, string label)
    {
        Console.WriteLine(label + ": " + s);
    }
}

//Utilização
string nome = "Israel";
nome.Escrever("Nome");

Ligeiramente o Visual C# tem uma pequena diferença na sintaxe, pois o primeiro parâmetro (que especifica o tipo em que o método será “adicionado”) deve estar pré-fixado com a keyword this. Finalmente, como podemos notar no exemplo acima, ao definir o extension method você já o tem disponível no Intellisense, com um ícone diferente em relação ao ícone padrão de um método qualquer do tipo.

Implicitly Typed Arrays

A criação implícita de arrays fortemente tipados também é uma exclusividade dessas novas versões das linguagens. Neste caso, o tipo de cada elemento é automaticamente deduzido de acordo com o tipo dos elementos onde, para isso, é necessário que no momento da inicialização do array, todos os elementos sejam, obrigatoriamente, do mesmo tipo ou que possam ser implicitamente convertidos. O exemplo abaixo ilustra como devemos proceder para criar este tipo de array:

var ids = new[] { 1.2, 2, 3, 4, 5 };
var nomes = new[] { "Israel", "Claudia", "Juliano", "Decio", "Leandro" };

Felizmente podemos combinar essa funcionalidade com algumas das funcionalidades acima, que são os tipos anônimos e os inicializadores de objetos e tornar o código um pouco mais performático e, em um primeiro momento, talvez um pouco estranho, mas com a utilização em nosso dia a dia veremos a produtividade e eficácia que isso nos proporciona. O exemplo abaixo cria um array onde em cada um dos elementos são adicionados um tipo anônimo:

var pessoas = new[]{
      new {
            Nome = "Israel", 
            Idade = 25
      },
      new {
            Nome = "Claudia", 
            Idade = 23
      }
};

Importante: Como até o presente momento o Visual Basic .NET não possui o recurso Implicitly Typed Arrays, o array pessoas possui no interior de cada elemento um tipo anônimo, mas o tipo efetivo de cada elemento é um System.Object que, vai ao contrário do que essa funcionalidade tenta fornecer, comportamento que não ocorre no Visual C#.

Lambda Expressions

Na versão 2.0 do Visual C# a Microsoft introduziu um conceito chamado métodos anônimos. Os métodos anônimos permitem escrevermos uma “expressão” de forma “in-line” em locais onde uma instância de um delegate é esperada. Apesar de citarmos alguns exemplos acima, vamos analisar um exemplo de método anônimo. Supondo que temos um delegate que recebe dois números inteiros e retorna um valor, também inteiro, temos as seguintes formas de proceder para executar o delegate nas versões .NET 1.x e 2.0 do .NET:

delegate int Operacao(int a, int b);

//Utilizando a versão 1.x
static void Main(string[] args)
{
    Operacao op = new Operacao(Soma);
    Console.WriteLine(op(2, 4));
}

public static int Soma(int a, int b)
{
    return (a + b) * 3;
}

//Utilizando a versão 2.0
static void Main(string[] args)
{
    Operacao op = new Operacao(delegate(int a, int b)
    {
        return (a + b) * 3;
    });

    Console.WriteLine(op(2, 4));
}

A primeira versão não tem muito o que comentar. É aquilo e pronto! Como podemos notar, com a versão 2.0 do Visual C#, o método para qual aponta o delegate não existe mais. Muitas vezes, criávamos um método exclusivamente para utilizar em conjunto com o delegate, poluindo a classe. Os métodos anônimos permitem-nos omitir o método que será executado quando o delegate for invocado, mas respeitando os parâmetros (em quantidade e tipo) que foram estipulados pelo delegate e que serão passados para o método anônimo. Depois de compilado, o compilador se encarrega de criar o método que, em tempo de desenvolvimento, omitimos e, além disso, se preocupa em definir todas as referências para invocá-lo corretamente.

Já as Lambda Expressions tornam os métodos anônimos muito mais concisos mas, se não ter um pouco de cuidado, pode parecer confuso em um primeiro momento. Essa é uma das principais funcionalidades que o LINQ utiliza em suas query expressions, fornecendo uma forma compacta e fortemente tipada de escrever funções, passar seus respectivos argumentos e, efetivamente, a sua avaliação (implementação).

Para exemplificar, vamos analisar como fica o código acima utilizando as Lambda Expressions:

delegate int Operacao(int a, int b);

static void Main(string[] args)
{
    Operacao op = (a, b) => (a + b) * 3;
    Console.WriteLine(op(2, 4));
}
C#  

Como podemos ver, ao invés de atribuirmos a instância propriamente dita do delegate Operacao para a variável op especificamos uma Lambda Expression. Da esquerda para a direita, (a, b) é a lista de parâmetros que foram especificados pelo delegate e, como já devem perceber, os tipos são inferidos, ou seja, na ordem em que eles aparecem na assinatura do delegate é que os seus respectivos tipos são atribuídos; em seguida temos o símbolo => que aparece sempre e, finalmente, uma expressão ou um bloco que será executado quando a expressão for invocada. É importante dizer que o nome dos parâmetros são completamente irrelevantes assim como qualquer método ou evento dentro da plataforma .NET. Como exemplo eu nomeei de a e b, mas poderia ser numero1 e numero2 ou algo de sua preferência.

O fato de podermos utilizar as Lambda Expressions impõe algumas regras que devem ser seguidas. Algumas dessas regras são exibidas através da listagem abaixo:

  • Parênteses: você pode omitir parênteses da lista de argumento somente quando existe apenas um único argumento (salvo quando quiser explicitamente definir o tipo). Quando o delegate possuir mais que um argumento ou quando o delegate for do tipo void, os parênteses devem ser especificados e, no caso do void, você deve definir um par de parênteses vazio.

  • Múltiplas linhas: é possível criar uma Lambda Expression que possui mais de uma linha. Para isso é necessário que você a envolva entre chanves { }.

Através dos códigos abaixo conseguimos ter uma idéia do poder das Lambda Expressions e, ao longo de sua utilização, a sintaxe fica amigável e rapidamente você já estará confortável em ler e entender o código.

delegate int Operacao(int a, int b);
delegate void WriteText(string text);
delegate void Teste();

static void Main(string[] args)
{
    Operacao op1 = (int a, int b) => a + b;
    Operacao op2 = (a, b) => a + b;

    WriteText wt1 = s => Console.WriteLine(s);
    WriteText wt2 = s =>
                        {
                            if (!string.IsNullOrEmpty(s))
                                Console.WriteLine(s);
                            else
                                Console.WriteLine("Valor não definido.");
                        };

    Teste t1 = () => Console.WriteLine("Teste");

    Console.WriteLine(op1(2, 3));
    Console.WriteLine(op2(3, 9));

    wt1("Israel - 1");
    wt2("Israel - 2");
    wt2(string.Empty);
    t1();
}

Baseando-se pelas explicações acima, acredito que você já consiga definir qual será o resultado. De qualquer forma, coloco logo abaixo o resultado gerado pelo código acima:

5
12
Israel – 1
Israel – 2
Valor nao definido.
Teste

A utilização das Lambda Expressions vai muito além dos exemplos que vimos aqui mas, analisaremos cada um destes cenários sob demanda, ainda neste artigo. Como você pode notar, os exemplos deste tópico são voltados exclusivamente para o Visual C#. Isso acontece porque a versão atual do Visual Basic .NET Express (Beta 1 – Orcas) ainda não suporta as Lambda Expressions. Apesar de já anunciado que até a versão Beta 2 ou Release o Visual Basic .NET suportará esta funcionalidade, optei por não abordá-la aqui justamente por não conseguir efetivamente testar.

Vídeos

Estes vídeos (Versão em C#/Versão em VB.NET) contemplam as novas funcionalidades que foram incluídas dentro da versão 3.5 do .NET Framework, mais precisamente na versão 3.0 do C# e versão 9.0 do Visual Basic .NET. Essas funcionalidades são extremamente utilizadas dentro do LINQ e você deve estar a vontade com elas antes de começar a trabalhar efetivamente com o mesmo.

Anúncios

3 comentários sobre “Explorando o LINQ – Background

    • Muito bom este post! Me ajudou a entender justamente o que eu estava procurando entender sobre o esquema de métodos anôminos e tópicos mais avançados de generics. Acho que esta parte de generics, delegates e reflection deveria continuar sendo tópico de outros posts, pois existem muito pouca matéria em português sobre o assunto, e nem sempre é fácil entender os termos técnicos em inglês principalmente pra quem está começando.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s