Friday 18 December 2009

Como usar Lambda Expressions em C#

Eu sei que esta funcionalidade já existe à algum tempo e eu até já tinha usado em alguns exemplos, mas, ainda não tinha pensado em Lambda Expressions a sério.
Assim, resolvi hoje pegar em alguns exemplos e encontrar um exemplo que me sirva e tentar fazer alguma coisa. O que descobri é que é mais um nível de abstração no nosso código e em alguns casos, transforma-o em código cada vez mais dificil de ler e noutros casos facilita imenso.

Tentei resolver um problema que tenho no meu código que é a obtenção de um Entidade de diversas formas. Todos nós temos em alguma parte do nosso código as seguintes expressões: GetEntityByName ou GetEntityByEmail, onde Entity é a uma qualquer entidade da nossa aplicação e a seguir ao By temos toda e qualquer propriedade que faça sentido.

Este tipo de abordagem leva a um problema: “Magic String” espalhados no nosso código, pois se não vejamos. Nesses métodos temos habitualmente o seguinte código:

        public string GetEntityByName(string name)
        {
            string sqlStatement = string.Format("SELECT * FROM <Entity Table> WHERE name = {0}", name); 
            (…)       
        }

ou ainda:

         public string GetEntityByEmail(string name)
        {
            string sqlStatement = string.Format("SELECT * FROM <Entity Table> WHERE email = {0}", email);      
            (…)
        }

Uma qualquer alteração na tabela não leva um erro em Compiletime na nossa aplicação, mas, em Runtime.

Será que não conseguimos melhorar esta abordagem usando as expressões do C# 3.0? Como?
Com este método:

        protected virtual string GetEntityByProperty(Expression<Func<T, object>> expression, object value)
        {
            string selectedProperty = PropertyUtil.GetPropertyByName<T>(expression);

            string sqlStatement = string.Format("SELECT * FROM <Entity Table> WHERE {0} = {1}", selectedProperty, value);

            (…)
        }

Desta forma consigo reproduzir qualquer GetObjectBy que a minha aplicação necessite. A criação da linha de comando SQL está centralizado num só método e qualquer alteração que tenha que ser feita a esta, é efectuada num só lugar.

O Helper que uso para obter a propriedade a partir de uma expressão é:

    public static class PropertyUtil
    {
        public static string GetPropertyByName<T> (Expression<Func<T, object>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("propertyRefExpr", "propertyRefExpr is null.");

            MemberExpression memberExpr = expression.Body as MemberExpression;
            if (memberExpr == null)
            {
                UnaryExpression unaryExpr = expression.Body as UnaryExpression;
                if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
                    memberExpr = unaryExpr.Operand as MemberExpression;
            }

            if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
                return memberExpr.Member.Name;

            throw new ArgumentException("No property reference expression was found.", "expression");

        }
    }

Na minha aplicação passo a invocar este processo da seguinte forma:

        public string GetEntityByName(string name)
        {
            var user = GetEntityByProperty(user => user.Name, name);
            (…)       
        }

ou ainda:

        public string GetEntityByName(string email)
        {
            var user = GetEntityByProperty(user => user.Email, email);
            (…)       
        }

 

Desta, qualquer alteração no objecto User irá levar a um erro em CompileTime que podemos corrigir facilmente.


Abraços
Paulo Aboim Pinto

No comments: