Os recursos aqui apresentados como Lambda Expression, Extension Methods e Nullable types (HasValue) são novas funcionalidades adicionadas ao C# 3.0, com exceção de Nullable types que já existia desde a versão do Framework 2.0, estes recursos nos ajudarão a codificar de uma forma mais limpa, legível, melhorando a forma de se implementar funções e adicionar novas funcionalidades a classes sem necessitar alterar seu código ou realizar uma herança, também apresentará um meio para que variáveis por referência possam armazenar valores indefinidos (nulos).

O tema é útil em cenários onde fazemos uso de funções anônimas como delegate, poderemos escrever um método anônimo de maneira mais sucinta e fácil para entender, também iremos ver como adicionar novas funcionalidades a um tipo existente, mesmo sem termos o código da classe disponível ou fazer uso de herança e por ultimo, mas não menos importante, nós iremos verificar como resolver um problema comum quando trabalhamos com banco de dados e precisamos retornar um valor do banco como indefinido (NULO) e o tipo esperado na propriedade que antes não poderia armazenar um valor nulo e agora poderá.

Lambda Expression

No desenvolvimento de aplicações, sejam elas a partir do zero, melhorias ou correções, sempre podemos aperfeiçoar nosso código fonte, e sempre teremos novas formas de como fazê-lo, e para começarmos com uma nova forma, vamos iniciar com Lambda Expression ou expressões lambdas, mas para isso, o que é um Lambda Expression?

Lambda expressions são funções anônimas e métodos anônimos com uma sintaxe mais sucinta, alguns programadores utilizam o termo sintaxe açucarada, pois a forma como é escrita facilita a legibilidade do código, para alguns programadores a mudança pode ser um pouco chocante (a menos aos que já tenham tido algum contato com linguagens funcionais como Lisp ou Haskell), como podemos observar na Figura 1, a expressão lamba encontra-se abaixo das funções anônimas, então logo uma expressão lambda não terá nome, assim como os métodos anônimos, você passara por essas expressões e irá achá-las parecida com os delegates, a diferença é que uma expressão lambda possui uma sintaxe mínima e concisa, podendo-se poupar mais tempo digitando código e veremos que tornara mais agradável a forma de embarcar métodos anônimos ao código. As expressões lambdas são dividas em dois tipos: Statement Lambdas e Expression Lambdas. Figura 1 mostra a hierarquia deste relacionamento.

Lambda Expression
Figura 1. Lambda Expression.
Nota: Funções Anônimas Segundo o C# Language Specification Versão 3.0, uma função anônima é uma expressão que representa a definição de uma instrução em linha, é convertível a um delegate compatível ou do tipo expression tree. Para convertermos uma função anônima, dependemos do tipo que será convertido, ou seja, se o tipo for delegate, a conversão será a chamada de um método, ou caso seja do tipo árvore de expressão (expression tree) representara a estrutura do método como uma estrutura de objetos, árvores de expressão estão além do escopo deste artigo, mas o que precisamos entender neste momento sobre eles é que uma expressão é um código (sim, isso mesmo, seu código C#) representado como dados ou objetos em uma aplicação .NET em execução.

Analisando a Expressão Lambda

Existem duas características ortogonais que as expressões lambdas podem ter:

( parâmetros de entrada ) => {expressão ou bloco de instrução}; 

Mas que negócio é esse aí no meio?

Bem isso aí no meio é o operador lambda =>, foi introduzido no C# 3.0, e lido como o “vai para” (goes to), a sintaxe é um pouco diferente então vamos quebrar está expressão em duas partes para a explicação: do lado esquerdo temos uma lista de parâmetros que podem ter tipos implícitos ou explícitos. a serem passados a um método. O lado direito representa o código do método (corpo) que será executado e que podem ser expressões ou blocos de instruções.

Abaixo um pseudocódigo, “NÃO TOTALMENTE EXATO”, apenas para captarmos a idéia principal.

// expressão lambda simples
X => Console.WriteLine(X.ToString());

 

Você pode pensar que este método está definido da seguinte forma:

// método falso representando a expressão lambda acima.
void Method (unknownType X)

{

    Console.WriteLine(X.ToString());

}

Na sua essência, sem nos aprofundarmos em detalhes técnicos, isso é o que o compilador está fazendo por nós quando encontra uma expressão lambda no código.

A lista de parâmetros é opcional em uma função anônima. Podendo ser tipos implícitos ou explícitos, vejamos alguns exemplos:

x => x + 1                   // tipo Implicito, expressão

x => { return x + 1; }             // tipo Implicito, bloco de instrução

(int x) => x + 1                   // tipo explicito, expressão

(int x) => { return x + 1; }       // tipo explicito, bloco de instrução

(x, y) => x * y                    // multiplos parametros

() => Console.WriteLine()          // Sem parametros

Criando um exemplo com Expressão Lambda

Vamos agora a um exemplo onde criaremos uma função que ao passar um valor inteiro ela nos retorne o valor + 2, para isso, vamos criar no Visual Studio 2008 um novo projeto do tipo Console Application, crie o projeto utilizando a linguagem C# como base, e de o nome do projeto de LambdaExpression, conforme figura 2.

Lambda Expression
Figura 2. Criando um exemplo com Expressão Lambda.

Com o projeto criado, insira o seguinte código da Listagem 1.

Listagem 1. exemplo de expressão lambda.
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LambdaExpression

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine(SomaDelegate(2));

            Console.WriteLine(SomaLambda(2));           

        }      

        //Exemplo utilizando delegate

        static Func SomaDelegate =   delegate( int i )

        {

              return i + 2;

         };

        //Exemplo utilizando expressão lambda

        static Func SomaLambda = i => i + 2;

    }

}

Ao executarmos este programa será impresso o valor 4 duas vezes, pois ambas as funções realizam a formula Valor + 2, com este exemplo podemos notar que a expressão lambda não utiliza mais a palavra chave delegate, ela utiliza o operador lambda =>, também podemos verificar que o tipo do argumento já não é mais redundante e as palavras chaves de retorno são omitidas, tornando assim o código mais limpo e a sintaxe mais agradável.

Modo antigo vs Modo Novo

Para garantirmos o entendimento desta nova característica Lambda Expressions incluída no C# 3.0, visto por muitos como uma sintaxe açucara, vamos trabalhar mais dois exemplos para que seja possível avaliarmos as diferenças entre o modo antigo e o novo modo de trabalhar com funções anônimas.

Vamos iniciar pelo modo antigo, criaremos uma classe OldStyle para realizar nossa implementação de acordo com a Listagem 3.

Primeiro temos de declarar o tipo do delegate, DoSomethingGood no nosso caso, e depois criaremos um método DoingSomethingGood que criara a instância do nosso delegate para que seja possível chamar o método alvo DoSomethingGoodImpl que conterá a ação de fato:

Listagem 3. modo antigo de se programar funções anônimas.
 
class OldStyle

    {

        // declare o delegate

        delegate int DoSomethingGood(string SomethingGood);

        // método para chamar o delegate

        public void DoingSomethingGood()

        {

            // declare a instancia

            DoSomethingGood dg = new DoSomethingGood(DoSomethingGoodImpl);

            // invoke delegate

            int i = dg("Help people old style");

        }

        // Método criado para o delegate chamar

        public int DoSomethingGoodImpl(string s)

        {

            Console.WriteLine(s);

            return s.GetHashCode();

        }

    }

As expressões lambda permitem a criação de código para execução quando um representante é chamado, não sendo necessário criar um método formal para a chamada do delegate, mas é necessário a implementação do mesmo para que seja realizado algo, neste caso o método não possui um nome (nameless), utilizando a expressão lambda, poderíamos implementar o código acima conforme a Listagem 4:

Listagem 4. Novo modo de se programar uma função anônima.

class LambdaStyle

    {

            // Criando o delegate

            delegate int DoSomethingGood(string work);

            // Método para criar uma instacia do delegate e a chamada do método

            public void DoingSomethingGood()

            {

                // instancia

                DoSomethingGood dg = s =>

                {

                    Console.WriteLine(s);

                    return s.GetHashCode();

                };

                // chamada do delegate

                int i = dg("Help people new style ");

            }     

    }

Note que neste caso não temos mais o método DoSomethingGoodImpl, utilizamos o operador lambda para associar diretamente a implementação do método DoSomethingGoodImpl para o delegate DoSomethingGood.

Extension Methods

Os métodos de extensão (Extension Methods) permitem ao desenvolvedor adicionar novos métodos a um tipo existente sem ter que criar uma herança, re-compilar o código fonte e mesmo sem termos o código da classe ao qual queremos adicionar um novo método, ou seja, podemos injetar novas funcionalidades a um tipo especifico (classes, estruturas, ou implementação de interfaces).

Nota: Temos de compreender que um método de extensão não muda literalmente o código compilado. Essa nova técnica só adiciona membros a um tipo dentro do contexto da aplicação atual.

Um método de extensão é definido do mesmo jeito que qualquer método, ou seja, para definirmos um método de extensão, temos de defini-los dentro de uma classe estática e, portanto um método de extensão também deve ser estático.

Todos os métodos de extensão são marcados como tal, usando a palavra chave this como um modificador do primeiro parâmetro do método em questão, ou seja, quando o primeiro parâmetro de um método for marcado com o modificado this, podemos dizer que este método é um método de extensão, porem existem algumas restrições para os métodos de extensão, eles não poderão ser declarados dentro de classes não genéricas e dentro de uma classe estática aninhada de nível mais baixo, ou seja, somente na classe estática de nível mais alto o método estático poderá ser declarado.

Cada método de extensão pode ser chamado a partir da instancia da classe na memória, ou estaticamente através da classe estática onde foi definido.

Aqui está a forma geral de um extension method:

  • static return-type name(this objeto chamado, lista de parametros)

Se caso não tivermos uma lista de parâmetros, esta lista poderá estar vazia. Lembre se que o primeiro parâmetro passado será sempre o objeto que o método irá chamar. Geralmente um extension method será um membro publico da classe.

Para entendermos melhor este conceito, vamos a um exemplo, Listagem 5, para este exemplo será necessário criar novamente um projeto do tipo Console Application, e para no nome do projeto ExtensionMethods:

Listagem 5. Métodos de extensão.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace ExtensionMethods

{

    class Program

    {

        static void Main(string[] args)

        {

            String texto = "Extension Methods";

            double valor = -999;

           

            // Uma variavel do tipo String, passa a apresentar o método InverteCase

            Console.WriteLine(texto + ", depois de invertido: " + texto.InverteCase());

            // Uma varial do tipo Double, passa a apresentar o método Modulo

            Console.WriteLine("Modulo do valor: " + valor.Modulo());

        }

    }

 

    //Classe estática com os métodos extendidos

    static class MetodoExtendido

    {

        ///

        /// Método que trocara letras Maiusculas por minusculas

        ///

        ///

        ///

        public static String InverteCase(this string str)

        {

            //variavel para guardar o valor temporario

            string temp = "";

            //percorro a lista de caracteres no texto str

            foreach (char ch in str)

            {

                //realizo a inversão de maiuscula e minuscula

                if (Char.IsLower(ch))

                    temp += Char.ToUpper(ch);

                else

                    temp += Char.ToLower(ch);

            }

            //retorno

            return temp;

        }

 

        ///

        /// Retorna o modulo de um valor

        ///

        ///

        ///

        public static double Modulo(this double valor)

        {           

            //biblioteca Math que contem o método Abs

            return Math.Abs(valor);

        }

    }

}

Neste programa, note que cada método de extensão pertence a classe estática Extension Methods. Como expliquei anteriormente, um método de extensão tem de ser declarado dentro de uma classe estática e essa classe tem de estar no escopo de onde o método será utilizado, note também que a chamada do método estendido, estará no próprio objeto, da mesma forma que um método instanciado é chamado. A diferença é que a chamada do objeto é passada para o primeiro parâmetro do método estendido.

Entendendo Tipos Nulos (Nullable types) no C#

O Tipo Nulo é utilizado toda vez que precisamos armazenar um valor nulo, ao analisarmos os tipos de dados presente na CLR encontraremos os tipos por valor e por referencia, um tipo de dado por valor seria, por exemplo, um inteiro, enquanto um tipo de dado por referencia seria, por exemplo, uma String, um tipo de dado por valor normalmente não armazena valores nulos, enquanto um tipo de dado por referencia aceita valores nulos, quando tentamos associar um valor nulo a uma variável do tipo inteiro, por exemplo, recebemos um erro em tempo de compilação dizendo que não é possível converter um tipo inteiro em um tipo de dado não nulo, mas isso pode ser resolvido, isso ocorre principalmente em cenários envolvendo banco de dados onde pode-se retornar valores não definidos para um tipo booleano pois normalmente em um banco de dados podemos guardar verdadeiro ou falso, ou poderá ser indefinido.Vejamos o exemplo de código abaixo, se tentarmos adicionar um valor nulo a um tipo por valor, termos um erro de compilação:


static void Main(string[] args)

{

   // Erro de compilação!

   // O tipo por valor não pode receber nulo.

   bool myBool = null;

   int myInt = null;

}

No C# o tipo nullo pode ser declarado com a adição de uma marcação de interrogação (?) ao tipo de dado (ex. int x?) ou utilizando a sintaxe System.Nullable onde T é o tipo de dado, podemos utilizar qualquer uma das formas, quando utilizamos um tipo nulo podemos utilizar a propriedade extra HasValue para verificarmos a existência de valor.


 static void Main(string[] args)

     {

         // Definição de variaveis locais para receberem valores nulos.

         int? IntNulo = 10;

         double? DoubleNulo = 3.14;

         //ou

         System.Nullable BoolNulo = null;

     }

Utilizando tipos nulos, podemos fazer uso da propriedade HasValue, está propriedade retorna um valor boleano, verdadeiro quando a váriavel possuir um valor não nulo.


 static void Main()

        {

            int? x = 10;

            //x possui valor?

            if (x.HasValue)

            {

                System.Console.WriteLine(x.Value);

            }

            else

            {

                System.Console.WriteLine("Indefinido");

            }

        }

Conclusão

Os tópicos aqui discutidos, são uteis em nosso dia a dia, facilitando o desenvolvimento e quando precisamos fazer adaptações e alterações ao código, ao entender as expressões lambdas, estaremos entendendo a base de consultas em métodos LINQ, uma expressão lambda é a maneira mais conveniente de criar delegates,os Métodos de extensão, poderão ser usados em manutenções e adaptações diversas quando precisarmos adicionar funcionalidades a um tipo ao qual não temos acesso ao código e reaproveitar tais funcionalidades em diversas partes do código, e os tipos nulos resolvem muitos problemas em operações principalmente com banco de dados que muitas vezes precisamos retornar e manipular valores nulos.