Extension Methods e legibilidade de código

Como o assunto DSL (Domain Specific Languages) ainda promete dar muito o que falar, parece que a gente fica cada dia mais influenciado pela tendência de deixar o código o mais próximo possível da linguagem de negócio.

Hoje, enquanto escrevendo um pequeno código de exemplo, acabei criando o seguinte teste:

[Test]
public void Aluno_DeveriaReceber100PorcendoDeGratuidadeCasoTenhaRendimentoAcimaDe90PorCento()
{
ConfiguracoesDoSistema.TipoDeGratuidade = TipoDeGratuidade.RendimentoAcademico;

Aluno bomAluno = new Aluno(“Bom Aluno”);
bomAluno.RendimentoAcademico = 95.Porcento();

Assert.AreEqual(100.Porcento(), bomAluno.GratuidadeConcedida.Valor);
}

Notem o uso da construção 100.Porcento(). Ela é possível através da utilização dos Extension Methods do C# 3.0. O código para gerar o extension method “Porcento” ficaria assim:


namespace Domain
{
public static class MyExtensionMethods
{
public static double Porcento(this int valor)
{
return (double)valor / 100;
}
}
}

O código do teste poderia estar ainda mais claro, isto é, sem interferências geradas pela sintaxe da linguagem, caso o C# suportasse o conteito de “Property Extensions”.
Se isso existisse, poderíamos escrever algo do tipo:

bomAluno.RendimentoAcademico = 95.Porcento;

De qualquer forma, ainda não estou convencido de que este código é mais legível. O que você acha?

C# 3.0 – Extension Methods

Suponha que, num método C#, tenhamos uma variável do tipo string chamada  “data”.
O que faz o trecho de código abaixo?

data.Substring(0, 2);

E o este aqui?

data.Substring(data.Length – 2);

O primeiro trecho de código retorna os 2 primeiros caracteres da string existente
na variável “data”. Já o segundo trecho de código retorna os últimos 2 caracteres
da mesma string.

A semântica imposta pelo código acima não denuncia a sua real intenção num primeiro
momento, correto? Seria, por acaso, mais fácil se tivéssemos o seguinte?

data.Left(2);

e

data.Right(2);

Na minha opinião, sim! E, na verdade, sempre senti falta desses métodos na BCL do
.NET. Uma alternativa seria criarmos uma nova classe derivada de System.String
e acrescentarmos esses métodos a essa classe. Pena que isso não é possível. A
classe string no .NET é selada (sealed) e, portanto, não pode ser estendida.

Felizmente, o C# 3.0 trará um recurso muito interessante chamado “Extension Methods”.
Esta funcionalidade da linguagem é altamente explorada pelo LINQ (Language Integrated
Query), mas vamos nos deter aqui ao funcionamento desse recurso na linguagem. É possível
usarmos exatamente o código apresentado acima lançando mão de Extension Methods. A
primeira coisa que temos que fazer é criar uma classe estática com os métodos Left
e Right (classes estáticas também são novas no C# 3.0 e são classes compostas somente
de métodos estáticos). O código abaixo mostra a definição dessa classe:

namespace MyExtensions
{
    public
static class
StringExtensions
    {
        public
static string
Left(this string text, int
numberOfCharacters)
       
{
            return text.Substring(0,
numberOfCharacters);
       
}
>



        public
static string
Right(this string text, int
numberOfCharacters)
       
{
            return text.Substring(text.Length
– numberOfCharacters);
       
}
    }
}

Note que os métodos são definidos com dois parâmetros, mas somente
um deles é efetivamente passado na chamada do método. O primeiro parâmetro de um Extension
Method, na realidade, define o tipo ao qual o método se aplica (no caso o tipo string).
Para que este método possa ser caracterizado pelo compilador como um Extension Method,
e não como um mero método com 2 parâmetros, a palavra reservada “this” é colocada
à frente do nome do tipo na declaração do método. Isso denota, assim, que os métodos
Left e Right são Extension Methods para o tipo string.

Como o nome já diz, Extension métodos estentem o comportamente
de um tipo. No caso, estamos estendendo o comportamento do tipo string. Mas não estamos
fazendo isso com todas as strings existentes em todos os nosso programas. Extension
Methods só são usados pelas classes que importam o namespace que contém os Extension
Methods. No nosso exemplo, os nossos Extension Methods estão definidos na classe “StringExtensions” do
namespace “MyExtensions“. Para que possamos usar esses
métodos em uma outra classe qualquer no nossso programa, temos que importar a namespace
“MyExtensions” e compilador se encarregará de descobrir quais são os Extension Methods
declarados nas classes deste namespaces, resolvendo-os em tempo de compilação.

O exemplo abaixo usa um método de teste para ilustrar a utilização dos nossos Extension
Methods:

using
System;
using
Microsoft.VisualStudio.TestTools.UnitTesting;
using MyExtensions;>

namespace
ExtensionMethodsTests
{
    [
TestClass
]
    public class
ExtensionMethodsTest
    {
        [
TestMethod
]
        public void
TestExtensionMethodsLeftAndRight()
        {
           
string
date = “2006-07-03″
;
           
string
year = date.Left(4);
           
string
day = date.Right(2);>

           
Assert
.AreEqual(“2006”
,
year);
           
Assert
.AreEqual(“03”,
day);
       
}
    }
}

>

Note que, para que as chamadas aos Extension Methods funcionem, precisamos importar
o namespace “MyExtensions”. Mas isso é tudo o que precisamos fazer.

Cool, huh?