martes, 29 de diciembre de 2015

Generación de metainformación de elementos de código.

Así que la ultima versión de C# incorpora nameof como palabra clave para poder quitarnos de en medio bastantes de las horribles cadenas mágicas que el framework nos obliga a escribir.

Muy poco a poco, gracias a los genéricos, expresiones lambda, funciones y tipos anónimos, dynamic, expando, etc, nos estamos quitando una enorme cantidad de cadenas mágicas del código.

En mi opinión nameof se queda corto. Yo por mi parte llevo años utilizando una plantilla T4 que se encarga de generar metainformación de las estructuras de datos de un proyecto en tiempo de diseño, para luego poder utilizarla en tiempo de ejecución sin tener que usar ni la lenta reflexión en tiempo de ejecución ni las horribles cadenas, que me organizarán un pifostio enorme el día que cambie una triste clase en código fuente. A ver cuando MS se pone las pilas y hace algo parecido pero que este bien integradito en el VS y tenga más flexibilidad.

Voy a explicar los fundamentos básicos para crear la plantilla T4 de generación de metainformación para el que esté interesado.

La primera cosa importante es decirle al procesador de la plantilla que le pase el host (en este caso el VS) para que podamos trabajar con el.

<#@ template language="C#" hostSpecific="true" debug="true" #>

Para acceder a los proyectos casteamos el host a IServiceProvider, obtenemos el servicio y lo casteamos a EnvDTE.DTE, que es el objeto de entrada del VS.

<#@ assembly name="EnvDTE" #>
<#@ Import Namespace="EnvDTE" #>

IServiceProvider hostServiceProvider = (IServiceProvider)Host;
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
EnvDTE.ProjectItem containingProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
Project project = containingProjectItem.ContainingProject;

Una vez tenemos el proyecto en el que está esta plantilla, podemos listar los items del mismo para procesarlos.

foreach (ProjectItem projectItem in project.ProjectItems)
{
//do things
}

Warning: Los projectItems pueden contener otros projectItems

Cada projectItem tiene un FileCodeModel que es la raíz de todos los modelos de código que hay en el item de proyecto. Así podemos recorrer todos los elementos de código que existen.

FileCodeModel fileCodeModel = projectItem.FileCodeModel;
foreach (CodeElement codeElement in fileCodeModel.CodeElements)
{
//do things
}

Cada codeElement es de un tipo que podemos descubrir gracias a un atributo con su tipo:

switch(codeElement.Kind) {
    case vsCMElement.vsCMElementNamespace: { }
    case vsCMElement.vsCMElementClass: { }
    case vsCMElement.vsCMElementInterface: { } 
    case vsCMElement.vsCMElementFunction: { }
    //etc
}

Y así, poco a poco y con un poco de trabajo podemos procesar los elementos de código y generar su metainformación de esta guisa:

[GenerateMetadata]
public class Clase
{
    public Clase()
    {
       
    }
 
    public int unAtributo;
    public string unaPropiedad { getset; }
    public byte unaFuncion() { return 0; }
 
    private int unAtributoPrivado;
    private string unaPropiedadPrivada { getset; }
    private byte unaFuncionPrivada() { return 0; }
}

internal static class ClaseMetadata
{
    public static class MemberNames
     {
        /// <summary>Name of field <see cref="Clase.unAtributo"/></summary>
        public const string unAtributo = "unAtributo";
 
        /// <summary>Name of property <see cref="Clase.unaPropiedad"/></summary>
        public const string unaPropiedad = "unaPropiedad";
 
        /// <summary>Name of method <see cref="Clase.unaFuncion"/></summary>
        public const string unaFuncion = "unaFuncion";
 
        /// <summary>Name of field <see cref="Clase.unAtributoPrivado"/></summary>
        public const string unAtributoPrivado = "unAtributoPrivado";
 
        /// <summary>Name of property <see cref="Clase.unaPropiedadPrivada"/></summary>
        public const string unaPropiedadPrivada = "unaPropiedadPrivada";
 
        /// <summary>Name of method <see cref="Clase.unaFuncionPrivada"/></summary>
        public const string unaFuncionPrivada = "unaFuncionPrivada";
 
     }
}

Este proceso, claramente, puede ser muy pesado en caso de proyectos con muchos items, por lo que la solución se basa en marcar las clases para las que necesitemos generar metainformación con un atributo. En nuestra plantilla T4 controlaremos si la clase tiene ese atributo para decidir si saltárnosla o procesarla.

No hay comentarios:

Publicar un comentario