Desenvolvimento - C#

Explorando o LINQ

Aqueles que acompanham a evolução das linguagens Visual Basic .NET e Visual C# notaram grandes melhorias entre as versões 1.x e 2.0 do .NET Framework, podemos dizer que a principal delas é o LINQ - Language Integrated Query.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Aqueles que acompanham a evolução das linguagens Visual Basic .NET e Visual C# notaram grandes melhorias entre as versões 1.x e 2.0 do .NET Framework. Uma das principais que podemos destacar é a criação dos Generics. Com uma nova versão do .NET Framework saindo, a 3.5, a Microsoft decidiu lançar junto com ela as novas versões das linguagens que mencionamos anteriormente, que possuem grandes melhorias que ajudaram bastante na escrita de código.

Além dessas várias funcionalidades, podemos dizer que a principal delas é o LINQ - Language Integrated Query. LINQ trata-se de uma sintaxe de consultas, bem próximo ao que já conhecemos atualmente com o SQL, que a Microsoft integrou, inicialmente, nas linguagens Visual C# e Visual Basic .NET. O LINQ utiliza quase todas as novas funcionalidades que foram adicionadas nas respectivas linguagens e é justamente sobre essas funcionalidades que formam a base para o LINQ que vamos abordar na primeira seção do capítulo.

É importante dizer que este artigo está baseado nas seguintes versões: Microsoft Visual C# Express Beta 1 (Orcas) e Microsoft Visual Basic .NET Express Beta 1 (Orcas). Há algumas funcionalidades que ainda só existem no Visual C# mas há a possibilidade de existir no Visual Basic .NET, até a versão Beta 2 ou Release e, sendo assim, eu optarei por deixar sem os respectivos exemplos mas atualizarei assim que obtiver a definição final.

Este artigo será publicado em partes, abaixo segue a relação das seções que serão abordadas com os respectivos vídeos. Nos vídeos eu dou uma visão macro de cada uma das funcionalidades. A idéia do vídeo é complementar o material escrito e não o contrário. Sendo assim, meu conselho é primeiramente ler a seção, entender sobre cada funcionalidade para depois recorrer ao vídeo e acompanhar como a implementação acontece.

Conteúdo

Background
Antes de iniciarmos o estudo efetivo sobre LINQ, é necessário que tomemos conhecimento de alguns recursos que foram adicionados nas linguagens Visual Basic .NET (versões 8.0 e 9.0) e Visual C# (versões 2.0 e 3.0). Tudo isso foi introduzido entre as versões 2.0 e, principalmente, 3.5 do .NET Framework. Entre esses novos recursos, podemos destacar, no caso do Visual C#, os métodos anônimos; além disso, temos alguns delegates Action e Predicate que foram introduzidos juntos à versão 2.0 do .NET Framework. As principais e mais notáveis funcionalidades em ambas as linguagens estão contidas na versão 3.5, que abordaremos também nesta seção.

Introdução
Esta seção é dedicada à introdução ao LINQ. Ela abordará a nova sintaxe e infraestrutura que foram introduzidas em cada uma das linguagens para suportar este novo recurso. Além disso, vamos entender como devemos proceder para manipular uma coleção de um determinado objeto, aplicando as operações mais comuns que temos em nosso dia a dia em cima desta coleção. É importante dizer que, como pré-requisito, esta seção depende muito das funcionalidades que foram extensivamente explicadas na seção anterior.

Operadores
Standard Query Operators é uma API que possibilita efetuarmos consultas em qualquer coleção (array), mais especificamente, em qualquer objeto que implementa a Interface IEnumerable. Esta API utiliza imensamente os Generics, que trata-se de um novo recurso que foi incorporado junto ao CLS (Common Language Specification) da versão 2.0 do .NET Framework. Dentro desta seção analisaremos cada um dos operadores disponíveis que podemos fazer uso dentro das aplicações.

LINQ To SQL
LINQ To SQL de uma das principais funcionalidades que foram adicionadas na versão 3.5 do .NET Framework. LINQ To SQL fornece uma infraestrutura para gerenciar (mapear) os dados relacionais para os objetos da aplicação e vice-versa. Esta seção abordará como devemos proceder para criar esse vínculo entre os objetos da aplicação e as colunas/tabelas de um determinado banco de dados; depois disso, analisaremos a inserção, atualização e exclusão dos dados, inclusive executando essas operações em um ambiente transacionado.

Background [ vídeo C# ] [ vídeo VB.NET ]

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;
Public Class ClasseGenerica(Of T)

    Private _valor As T

    Public Property Valor() As T
        Get
            Return Me._valor
        End Get
        Set(ByVal value As T)
            Me._valor = value
        End Set
    End Property

End Class

"Utilização:
Dim classe1 As New ClasseGenerica(Of String)
classe1.Valor = ".NET"

Dim classe2 As New ClasseGenerica(Of Integer)
classe2.Valor = 123
C# VB.NET
function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i 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>
{
    //....
}
Public Class ClasseGenerica(Of TStream)
    "....
End Class
C# VB.NET

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;
Dim dataDemissao As Nullable(Of DateTime)
dataDemissao = Nothing
C# VB.NET

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;
        }
    }
}
Public Class Usuario

    Private _nome As String

    Public Sub New(ByVal nome As String)
        Me._nome = nome
    End Sub

    Public Property Nome() As String
        Get
            Return Me._nome
        End Get
        Set(ByVal value As String)
            Me._nome = value
        End Set
    End Property

End Class
C# VB.NET

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);
    }
}
Module Module1
    Sub Main()
        Dim users As New List(Of 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(Of Usuario)(AddressOf Write))
    End Sub

    Public Sub Write(ByVal user As Usuario)
        Console.WriteLine(user.Nome)
    End Sub
End Module
C# VB.NET

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);        
        }));
    }
}
C#
function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i 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";
    }
}
Module Module1
    Sub Main()
        Dim users As New List(Of 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"))

        Dim busca As Usuario = nomes.Find(New Predicate(Of Usuario)(AddressOf Search))
        If Not IsNothing(busca) Then
            Console.WriteLine(busca.Nome)
        End If
    End Sub

    Public Function Search(ByVal user As Usuario) As Boolean
        Return user.Nome = "Anna"
    End Function
End Module
C# VB.NET

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);
    }
}
C#

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);
    }
}
Module Module1
    Sub Main()
        Dim users As New List(Of 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(Of Usuario)(AddressOf Sort))
        users.ForEach(New Action(Of Usuario)(AddressOf Write))
    End Sub

    Public Function Sort(ByVal u1 As Usuario, ByVal u2 As Usuario) As Integer
        Return Comparer(Of String).Default.Compare(u1.Nome, u2.Nome)
    End Function

    Public Sub Write(ByVal user As Usuario)
        Console.WriteLine(user.Nome)
    End Sub
End Module
C# VB.NET

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);
        }));
    }
}
C#

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);
    }
}
Module Module1
    Sub Main()
        Dim users As New List(Of 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"))

        Dim nomes As List(Of String) = _
            users.ConvertAll(Of String)(New Converter(Of Usuario, String)(AddressOf Conversao))

        nomes.ForEach(New Action(Of String)(AddressOf Write))
    End Sub

    Public Function Conversao(ByVal user As Usuario) As String
        Return user.Nome
    End Function

    Public Sub Write(ByVal nome As String)
        Console.WriteLine(nome)
    End Sub
End Module
C# VB.NET

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);
        }));
    }
}
C#

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. function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i 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;
Dim id = 123
Dim nomes = New String() {"Israel", "Claudia", "Juliano"}
Dim valor = 12.0
C# VB.NET

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;
Dim id As Integer = 123
Dim nomes As String() = New String() {"Israel", "Claudia", "Juliano"}
Dim valor As Double = 12.0
C# VB.NET

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...
}
C#

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" };
Public Class Usuario
    Public Id As Integer
    Public Nome As String
End Class

"Utilização

Dim user As New Usuario() With {.Id = 1, .Nome = "Israel"}
C# VB.NET

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);
Dim u = New With {.Nome = "Israel", .Idade = 25}
Console.WriteLine(u.Nome)
C# VB.NET

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");
Imports System.Runtime.CompilerServices

Public Module Helper
    <Extension()> _
    Public Sub Escrever(ByVal s As String, ByVal label As String)
        Console.WriteLine(label & ": " & s)
    End Sub
End Module

"Utilização
Dim nome As String = "Israel"
nome.Escrever("Nome")
C# VB.NET

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. function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i 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" };
"No caso do VB.NET, não é exatamente a mesma coisa do C#, que podemos omitir o tipo.
"Neste caso, precisamos definir o tipo explicitamente. Ao meu ver, não é exatamente
"a mesma funcionalidade (Implicitly Typed Arrays) pois, se ocultarmos o tipo,
"os elementos do array passam a ser do tipo System.Object.

Dim ids = New Integer() {1, 2, 3, 4, 5}
Dim nomes = New String() { "Israel", "Claudia", "Juliano", "Decio", "Leandro" }
C# VB.NET

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
      }
};
Dim pessoas() = { _
                  New With {.Nome = "Israel", .Idade = 25}, _                  
                  New With {.Nome = "Israel", .Idade = 25} _
                }
C# VB.NET

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));
}
C#

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();
}
C#

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.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.