Operador Ternário

Ao contrário das versões anteriores, a mais nova versão do Visual Basic – 9.0 – possui um verdadeiro operador ternário:

Module Module1
    Sub Main()
        Dim u As Usuario
        Dim b = If(u Is Nothing, String.Empty, u.Nome)
        Console.WriteLine(b)
    End Sub

    Public Class Usuario
        Public Nome As String
    End Class
End Module

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.

A evolução dos Delegates

Em tão pouco tempo o delegate evoluiu muito dentro da plataforma .NET. 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 respectivas versões do .NET até o momento:

delegate int Operacao(int a, int b);

[ 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;
}

[ 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));
}

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

A primeira versão não tem o muito o que comentar. É aquilo e pronto! Já a versão 2.0 do .NET permitiu a criação de métodos anonimos, o que evita de criarmos um método auxiliar (Soma) para vincularmos ao delegate. Já a versão 3.5 vai além. Como podemos notar, especificamos os parametros antes do operador “=>” (e os tipos são inferidos de acordo com a assinatura do delegate). Após esse operador é onde efetivamente colocaremos o código do método.

Isso é usado extensivamente quando utilizamos o Linq. Particularmente, a primeira vista eu também achei um bocado complexo mas praticando (nem que for por brincandeira) voce se adapta rapidamente.

Boas Práticas de Programação

Para termos um código bem enxuto e com boa qualidade não basta apenas termos convenção de nomes, classes, variáveis, etc.. Precisamos ter um padrão para algumas situações para melhorarmos não somente a performance do código que está sendo escrito, mas também a qualidade de ordenação para que, futuros desenvolvedores, ao olharem o nosso código em uma manutenção, consigam facilmente identificar e assim trabalhar em cima do problema e não reaprender tudo o que fizeram, disperdiçando assim tempo e dinheiro.

Veremos no decorrrer desta seção algumas técnicas que não são muito utilizadas no dia-a-dia dos programadores pois são pequenos detalhes que influenciam na performance e na escrita de um bom código. Essas boas práticas vão desde a ter um código elegante até como melhorá-lo, mas claro, estaremos abordando isso superficialmente. Além do code-tunning falaremos um pouco sobre Exceptions.

Operadores de curto-circuito

Operadores que operam em curto-circuito é uma exclusividade de algumas linguagens de programação e, felizmente, entre elas estão o C# e o Visual Basic .NET. Esses operadores ajudam-nos a poupar verificações desnecessárias em nosso código, pois se algumas das condições falharem, a outra nem é executada.

Existem operadores AndAlso (&&) e OrElse (||) que operam em curto-circuito, os quais veremos alguns exemplos abaixo:

if(condicao1 && condicao2){ //… }

if(condicao1 || condicao2){ //… }

Como podemos analisar no código acima, os operadores de curto-circuito nos ajudam a ter uma melhor performance, já que no caso do operador AndAlso (&&), se a condição 1 falhar, a segunda condição não será avaliada. Já no caso do operador OrElse (||), se a primeira condição for verdadeira, a segunda também não será executada, pois o resultado já está formado.

Teste de ordenação lógica

Um detalhe bastante importante que muitas vezes não nos atentamos é quando utilizamos o bloco switch (Select Case em VB.NET). Neste caso, o ideal é sempre ordenarmos a lista de possibilidades da mais freqüente para a menos freqüente. Isso evitará que a avaliação seja feita em vários itens, tendo assim uma perda de performance, pois se o item freqüente está no último item a ser avaliado, ele deverá passar por todos os outros antes.

Para exemplificar faremos um teste em que vamos analisar o tempo que é levado para que o valor que está sendo procurado seja encontrado dentro da lista.

class Program
{
    private static void TesteCase(string value)
    {
        switch (value)
        {
            case "A":
                ProcessarValor(value);
                break;
            case "1":
                ProcessarValor(value);
                break;
            case "B":
                ProcessarValor(value);
                break;
            case "2":
                ProcessarValor(value);
                break;
            default:
                break;
        }
    }
}

Como vemos no código acima, se estivermos procurando pelo valor “2” dentro da lista de possibilidades do método TesteCase e, como ele é o último item e o número de pesquisa por ele for relativamente grande, então teríamos aqui uma pequena perda de performance. Para vermos o resultado veremos a média de tempo em 10 consultas com o valor “2” sendo o último item, e depois sendo o primeiro item da lista:

Último da Lista Primeiro da Lista
1 – 00:00:00.0005578 1 – 00:00:00.0005039
2 – 00:00:00.0005514 2 – 00:00:00.0004975
3 – 00:00:00.0005520 3 – 00:00:00.0004961
4 – 00:00:00.0005514 4 – 00:00:00.0004967
5 – 00:00:00.0005528 5 – 00:00:00.0004955
6 – 00:00:00.0005514 6 – 00:00:00.0004978
7 – 00:00:00.0005796 7 – 00:00:00.0004972
8 – 00:00:00.0005517 8 – 00:00:00.0004969
9 – 00:00:00.0005520 9 – 00:00:00.0004975
10 – 00:00:00.0005517 10 – 00:00:00.0004967

Fusão de Loops

A fusão de loops é quando usam-se dois loops distintos para operar o mesmo conjunto de elementos e, em cada um deles, efetuar uma ação diferente. Na maioria das vezes utilizamos isso em coleções para alterar algum valor, ou algo do tipo. Abaixo podemos visualizar o código que está com o problema e, em seguida, o código já melhorado:

for(int i = 0; i < dados.Count; i++)
{
    dados[i].Nome = string.Empty;
}

for(int i = 0; i < dados.Count; i++)
{
    dados[i].Id = -1;
}

A melhor opção para este código é:

for(int i = 0; i < dados.Count; i++)
{
    dados[i].Nome = string.Empty;
    dados[i].Id = -1;
}

Minimizando o trabalho dentro de Loops

Este é um dos pontos essenciais para ganharmos em performance na aplicação. Muitas vezes colocamos operações custosas dentro de loops, o que acarretará na execução desta operação o mesmo número de vezes que o loop for executado. Na maioria dos casos, esse código custoso faz sempre a mesma coisa, ou seja, é um cálculo que independe de qualquer valor proveniente do loop. Se analisarmos o código abaixo, veremos o problema:

for(int i = 0; i < dados.Count; i++)
{
    dados[i].Taxa = GeraTaxa() * 2.25;
}

Se executarmos o código acima, a função GeraTaxa multiplicada pelo valor 2.25 será executada o número de vezes que o loop acontecer. Como neste caso, o valor será sempre o mesmo, o ideal é você isolar o cálculo fora do loop e, conseqüentemente, ter um código mais performático:

double taxa = GeraTaxa() * 2.25;
for(int i = 0; i < dados.Count; i++)
{
    dados[i].Taxa = taxa;
}

Minimizando o acesso à Arrays

Um outro detalhe importante é a referência de arrays dentro de loops. Se for mal projetado você pode ter problemas de performance, já que você fará o acesso a algum índice de acordo com o número de iterações do teu loop. O exemplo de código abaixo, mostra essa deficiência:

for(int i = 0; i < dados.Count; i++)
{
    for(int j = 0; j < dados[i].Items.Count; j++)
    {
        dados[i].Items[j].Valor = dados[i].Valor + 2;
    }
}

A melhor opção para este código é mover o cálculo para fora do loop interno, já que o valor será proveniente do cálculo com um valor fornecido pelo loop principal. Sendo assim, o código fica da seguinte forma:

for(int i = 0; i < dados.Count; i++)
{
    double valor = dados[i].Valor;
    for(int j = 0; j < dados[i].Items.Count; j++)
    {
        dados[i].Items[j].Valor = valor + 2;
    }
}

Code Caching

Code caching significa salvar um determinado valor que é proveniente de algum cálculo mais complexo em um membro interno que, por sua vez, será exposto pela classe. Geralmente utilizamos essa técnica quando o valor é freqüentemente utilizado e, se for sempre calculado, teremos uma perda de performance, já que o cálculo seria efetuado o mesmo número de vezes que a propriedade é invocada.

Essa técnica exige que dentro da propriedade que expõe o valor “cacheado” devemos efetuar uma verificação para saber se o valor já foi ou não calculado. Para exemplificar, veremos abaixo um exemplo de como colocamos essa técnica em prática:

internal class Funcionario
{
    private double _salario;
    private bool _salarioGerado;

    public double Salario
    {
        get
        {
            if (!this._salarioGerado)
            {
                this._salario = CalcularSalario(this.FuncionarioId);
                this._salarioGerado = true;
            }
            return this._salario;
        }
    }
}

Como podemos ver, a primeira vez que a propriedade é chamada, a condicional verifica se o membro _salarioGerado é atendida, ou seja, verifica se o salário já foi ou não gerado. Se ainda não tiver sido gerado, o método CalcularSalario é executado, passando para o mesmo o identificador do funcionário para efetuar o cálculo do salário. O retorno deste método é armazenado no membro privado _salario, definindo o membro _salarioGerado para True. Sendo assim, da segunda vez que a propriedade é invocada pelo consumidor da classe, o cálculo, que é a parte mais custosa do código, não será mais executado devido ao flag que estará como True.

É natural que, em algum momento, você precise reiniciar o valor, pois algum processo dentro da classe necessite que o salário seja recalculado e, para isso, você pode simplesmente voltar o valor do membro privado _salarioGerado para False.

Magic Numbers

Os magic-numbers são aqueles números que temos no código que referenciam índices de arrays, campos de colunas do banco de dados, contadores, etc.. Em alguns casos, como por exemplo, o acesso aos campos do DataReader, a utilização de números para referenciar as colunas do result-set é sempre mais performático do que passar o nome do campo mais, muitas vezes, isso dificulta a legibilidade do código, principalmente se precisar dar manutenção neste código mais tarde.

Este é um cenário típico para o uso de variáveis constantes do tipo inteiro, que devemos especificar o nome do campo da base de dados e definir o número correspondente que este campo está dentro do result-set. Abaixo podemos ver um exemplo de como isso funciona:

#region Colunas da DB
const int ID = 0;
const int NOME = 1;
const int EMAIL = 2;
#endregion

Cliente c = new Cliente();
//....
c.Email = dr.GetString(EMAIL);
c.ID = dr.GetInt32(ID);
c.Nome = dr.GetString(NOME);

Isso não afetará em nada a performance da aplicação já que quando o código é compilado, o compilador se encarrega de trocar as constantes pelo valor correspondente.

Exceções

O foco desta seção não é abordar como é feito o tratamento de exceções no .NET, mas sim entender algumas das boas práticas e também algumas dicas com relação à performance.

Um ponto importante é que nem sempre uma exceção representa um erro. Exceção é uma violação de alguma suposição da interface do seu tipo. Por exemplo, ao projetar um determinado tipo, você imagina as mais diversas situações em que seu tipo será utilizado, definindo também seus campos, propriedades, métodos e eventos. Como já sabemos, a maneira como você define esses membros torna-se a interface do seu tipo.

Assim sendo, dado um método chamado TransferenciaValores, que recebe como parâmetro dois objetos do tipo Conta e um determinado valor (do tipo Double) que deve ser transferido entre elas, precisamos “validá-los” para que a transferência possa ser efetuada com êxito. O desenvolvedor da classe precisará ter conhecimento suficiente para implementar essa tal “validação” e não esquecer do mais importante: documentar claramente para que os utilizadores deste componente possam implementar o código que fará a chamada ao método da maneira mais eficiente possível, poupando ao máximo que surpresas ocorram em tempo de execução.

public static void TransferenciaValores(Conta de, Conta para, double valor)
{
    //....
}

Dentro deste nosso cenário, vamos analisar algumas suposições (“validações”) que devemos fazer para que o método acima possa ser executado da forma esperada:

  • Certificar que de e para não são nulos;
  • Certificar que de e para não referenciam a mesma conta;
  • Se o valor for maior que zero;
  • Se o valor é maior que o saldo disponível.

Necessitamos agora informar ao chamador que alguma dessas regras foi violada. Mas como fazemos isso? Atirando uma exceção. Como dissemos logo no começo desta seção, ter uma exceção nem sempre é algo negativo na aplicação, pois o tratamento de exceções permite capturar a exceção, tratá-la e a aplicação continuará correndo normalmente.

Capturando e atirando Exceções

Um erro bastante comum é utilizar blocos catch em demasia. Ao capturar uma exceção, você informa ao runtime o que espera por aquela exceção, entendendo o porque aquilo aconteceu, definindo uma diretiva para a aplicação.

try
{
    //código que pode eventualmente
    //atirar uma exceção
}
catch(Exception)
{
    //...
}

Como pode notar, o código acima espera que ocorra qualquer tipo de exceção para que ele o trate. Qualquer tipo que faça parte de uma biblioteca de classes, nunca deve capturar a exceção mais genérica, pois não há maneira da aplicação chamadora ser notificada de que algum erro ocorreu.

Se o código envolvido pelo código try lançar uma exceção, a mesma deverá ser lançada até o topo da pilha de chamadas e deixar o nível mais alto tratar a exceção da maneira que desejar.

Quando falamos em lançar a exceção devemos nos atentar em como realizar essa tarefa. Isso é feito através da keyword throw, onde você deve desenhar a sua estrutura sem especificar nenhuma exceção no bloco catch, ou seja, fazer todo o trabalho que necessita, ou melhor, voltar o objeto utilizado em um estado consistente e, finalmente, notificar o chamador que uma exceção aconteceu, atirando-a, sendo ela qual for.

try
{
    obj.Validate();
}
catch
{
    obj.ResetValues();
    throw;
}

Validações

Evite sempre o bloco de tratamento de exceções Try/Catch para fazer validações simples, como por exemplo cálculos, conversões, etc.. Há sempre uma alternativa mais performática e ao mesmo tempo mais elegante de fazer essas operações para evitar todo o overhead que existe em um bloco Try/Catch. Para exemplifcar isso, podemos citar alguns exemplos de códigos onde teremos primeiramente o código ruim e, em seguida, o mesmo código já reformulado:

Código ruim

try
{
    IReader reader = (IReader)e.Data;
    reader.ExecuteOperation();
}
catch (InvalidCastException)
{
    Response.Write("Tipo incompatível.");
}

try
{
    int id = Convert.ToInt32(Request.QueryString["Id"]);
    this.BindForm(id);
}
catch (InvalidCastException)
{
    Response.Write("Id inválido.");
}

Código reformulado

IReader reader = (IReader)e.Data as IReader;
if(reader != null)
{
    reader.ExecuteOperation();
}
else
{
    Response.Write("Tipo incompatível.");
}

int id = 0;
if(int.TryParse(Request.QueryString["Id"], out id))
{
    this.BindForm(id);
}
else
{
    Response.Write("Id inválido.");
}

Conclusão: Boas práticas de programação são sempre bem-vindas em qualquer tipo de linguagem. Claro que as técnicas não páram por aqui. Existem muitas outras técnicas e benefícios relacionados a cada uma delas que este artigo não contempla. Este artigo dá apenas uma visão de técnicas que devem ser utilizadas no desenvolvimento de aplicações baseadas na plataforma .NET para tirar um melhor proveito da linguagem, não perdendo performance.

Extension Methods em VB.NET

Semana passada um aluno me perguntou como ele deveria proceder para criar um Extesion Method no Visual Basic .NET (Orcas). Ao contrário do C#, o Visual Basic .NET 9.0 necessita que o método estático a ser uma extensão de um determinado tipo, seja decorado com um atributo chamado ExtensionAttribute, que está contido dentro do namespace System.Runtime.CompilerServices (Assembly System.Core.dll). O exemplo abaixo mostra a sua utilização:

Imports System.Runtime.CompilerServices

Module Helper
    <Extension()> _
    Public Function RecuperaCincoPrimeiros(ByVal value As String) As String
        Return value.Substring(0, 5)
    End Function
End Module

Module Module1
    Sub Main()
        Dim valor As String = “1234567890”
        Console.WriteLine(valor.RecuperaCincoPrimeiros())
    End Sub
End Module

A única coisa que não gostei é que todo extesion method deve, obrigatoriamente, ser definido dentro de um módulo. Não sei porque a Microsoft não permite a criação de extension method dentro de uma classe, pois um módulo, depois de compilado, não deixa de ser uma classe concreta (NotInheritable), com todos os membros estáticos (Shared).

Na verdade, custa-me a acreditar que um módulo do VB.NET é equivalente a uma classe estática do C#. Imaginei que a Microsoft iria manter os módulos apenas por uma questão de compatibilidade com o VB6, mas está indo muito além disso…

“VBísses”

Há algumas coisas que são muito mágicas (atalhos? :)) no VB.NET em relação ao C#. Acredito que isso exista justamente por efeitos de compatibilidade em relação ao que já existia no passado, em outras versões do Visual Basic.

Dois detalhes especiais que sempre explico durante os treinamentos e que gera uma boa discussão, é com relação aos módulos e as variávies estáticas do Visual Basic [.NET]:

1 – Módulos: Quando compilado, vira uma classe concreta com todos os seus membros definidos como estáticos (Shared). Bem, concordo que isso acaba sendo uma espécie de “atalho”, mas o grande problema com eles é que não são OO em design-time, ou seja, não é possível herdar de um módulo ou herdar alguma classe em um módulo. É importante dizer que eu não conheço o VB 6 e não sei qual o comportamento (depois da compilação) dos módulos e classes.

2 – Variáveis estáticas: Quando compilado, as variáveis estáticas são elevadas a campos privados da classe corrente. A principal diferença entre as variáveis estáticas e os membros privados do campo é o escopo, pois as variáveis estáticas somente são acessíveis onde foram declaradas; já os membro privados, podem ser acessados dentro de qualquer membro dentro da mesma classe onde o mesmo foi declarado.

Lembrem-se que eu gosto bastante de VB.NET. O post é somente para direcionar as pessoas que estão começando em novos projetos a seguir por um caminho que, ao meu ver (IMHO), é o correto.