#retosMSDN: Solución al Reto 3 – Volcando objetos en C#

El pasado viernes Eduard Tomàs ( @eiximenis ), MVP en ASP.NET, nos propuso el tercero de los #retosMSDN: Reto 3 – Volcando objetos en C#. En primer lugar, quiero darle las gracias a Eduard por proponer un reto tan interesante que ha puesto a prueba nuestros conocimientos de C#. En segundo lugar, quiero daros las gracias una vez más a todos los que habéis participado en el reto.

 

La solución de Eduard

La solución que nos propone Eduard es muy completa, ya que combina el uso de Genéricos para que ObjectDumper pueda funcionar con cualquier tipo de objeto, Reflexión para poder obtener información del objeto y sus propiedades, LINQ y expresiones lambda para poder manipular la colección de propiedades del objeto de manera muy sencilla, expresiones de LINQ para poder obtener información de las expresiones lambda pasadas a AddTemplateFor como parámetro y poder llegar así a obtener el nombre de la propiedad a la que añadimos el template, y la palabra clave Yield para evitar el uso de colecciones intermedias en el método Dump:

 

 public class ObjectDumper<T>
{

    private readonly Dictionary<string, Delegate> _templates;

    public ObjectDumper()
    {
        _templates = new Dictionary<string, Delegate>();
    }

    public void AddTemplateFor<TR>(Expression<Func<T, TR>> propExp, Func<TR, string> template)
    {
        var property = propExp.AsPropertyInfo();
        if (property == null)
        {
            return;
        }
        _templates.Add(property.Name, template);

    }

    public IEnumerable<KeyValuePair<string, string>> Dump(T data)
    {

        if (((object)data) == null ) yield break;

        var dataType = data.GetType();
        foreach (var property in dataType.GetProperties().Where(p => p.CanRead).OrderBy(p=>p.Name))
        {
            var template = _templates.ContainsKey(property.Name) ? _templates[property.Name] : null;
            if (template != null)
            {
                yield return ApplyTemplateForProperty(property, data, template);
            }
            else yield return ApplyStandardDumpForProperty(property, data);
        }
    }

    private KeyValuePair<string, string> ApplyTemplateForProperty(PropertyInfo property, T data, Delegate template)
    {
        var value = property.GetValue(data);
        return new KeyValuePair<string, string>(property.Name, template.DynamicInvoke(value) as string);
    }

    private KeyValuePair<string, string> ApplyStandardDumpForProperty(PropertyInfo property, T data)
    {
        var value = property.GetValue(data);
        return new KeyValuePair<string, string>(property.Name, Convert.ToString(value));
    }

}

 

Y AsPropertyInfo lo ha definido como un método de extensión de la clase Expression:

 

 static class ExpressionExtensions
{
    public static PropertyInfo AsPropertyInfo<T, TR>(this Expression<Func<T, TR>> expr)
    {
        var memberExp = expr.Body as MemberExpression;
        if (memberExp == null)
        {
            return null;
        }

        return memberExp.Member as PropertyInfo;
    }
}

El código completo lo puedes encontrar en esta solución de Visual Studio 2013 que puedes descargarte de GitHub.

 

Vuestras soluciones

Y como no hay una única manera de hacer las cosas, a continuación puedes ver la solución que nos propuso @rsciriano:

 

 public class ObjectDumper<T> where T: class
{
    Dictionary<string, Delegate> templates = new Dictionary<string,Delegate>();

    public IEnumerable<KeyValuePair<string, string>> Dump(object source)
    {
        if (source == null)
        {
            yield break;
        }
        else
        {
            // Bucle por las propiedades publicas del objeto
            foreach (var prop in source.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty).OrderBy(p => p.Name))
            {
                // Solo trabajar con propiedades que tienen método Get
                if (prop.GetMethod != null)
                {
                    // Buscar template para la propiedad
                    Delegate templateDelegate;
                    if (templates.TryGetValue(prop.Name, out templateDelegate))
                    {
                        // Aplicar template al valor
                        yield return new KeyValuePair<string, string>(prop.Name, (string) templateDelegate.DynamicInvoke(prop.GetValue(source)));
                    }
                    else
                    {
                        // Llamar a ToString (teniendo en cuenta el valor null)
                        yield return new KeyValuePair<string, string>(prop.Name, prop.GetValue(source) != null ? prop.GetValue(source).ToString() : null);
                    }
                }
            }
        }
    }
    public void AddTemplateFor<TValue>(Expression<Func<T, TValue>> property, Func<TValue, string> value)
    {
        if (property != null)
        {
            // Obtener expresión para poder extraer el nombre de la propiedad
            MemberExpression exp = property.Body as MemberExpression;
                
            // Almacenar la template en el diccionario  
            if (exp != null)
                templates[exp.Member.Name] = value;
        }
    }
}

 

Además de todo lo que hemos mencionado que usa Eduard en su solución, puedes ver en esta solución el uso de la cláusula where al definir la clase genérica ObjectDumper. 

 

¡El próximo viernes 17 de octubre publicaremos el siguiente de nuestros #retosMSDN! Y si quieres retar al resto de la comunidad con tu propio reto, recuerda que puedes enviárnoslo a esmsdn@microsoft.com.

Un saludo,

Alejandro Campos Magencio (@alejacma)

Technical Evangelist

PD: Mantente informado de todas las novedades de Microsoft para los desarrolladores españoles a través del Twitter de MSDN, el Facebook de MSDN, el Blog de MSDN y la Newsletter MSDN Flash.