#dotNetSpain2015: Roslyn, el nuevo compilador de C# y Visual Basic

La conferencia dotNetSpain2015 ha sido el mayor evento de .Net que hemos hecho en España con más de 1000 asistentes. Allí, el pasado día 27 de Febrero, expliqué qué es y cómo podemos utilizar Roslyn, el nuevo compilador de C# y Visual Basic, para mejorar la calidad de nuestro código y de los usuarios de nuestras APIs. Aquí tenéis un resumen de la charla y algunos ejemplos. Os podéis descargar la presentación en el siguiente enlace.

 

¿Qué es Roslyn?

Es el nuevo compilador de C# y Visual Basic. Para crear el nuevo compilador hemos redefinido la arquitectura y hemos escrito los nuevos compiladores en sus propios lenguajes manejados, esto es en C# y Visual Basic respectivamente. Esto ha permitido abrir el compilador para proporcionar una API para cada una de las partes en que está compuesto el mismo:

  • Árbol de sintaxis: nos permite crear y recorrer el árbol sintáctico que está utilizando el compilador
  • Símbolos: analiza la estructura de nuestras clases sin necesidad de profundizar en un lenguaje en concreto
  • Binding y análisis de flujos: proporciona la información semántica de nuestro código, dónde, cuándo y cómo se están utilizando las clases y sus propiedades y métodos.
  • Emit: llegamos hasta la generación de código IL 

Al disponer de una API tan rica, se han creado una serie de servicios de compilador para mejorar la interacción con las herramientas de desarrollo. El compilador es ahora el encargado de realizar la comprobación de sintaxis, de completar el código, de realizar los refactors y muchas cosas más, tanto dentro de Visual Studio como a través de la línea de comando.

Roslyn API and Services

Open Source

Durante el pasado Build 2014 publicamos el código fuente del compilador en CodePlex y en enero de 2015 hemos movido el código a GitHub. El código está publicado bajo licencia Apache 2.0, una de las licencias más abiertas que hay.

Al tener el código del compilador, además de la transparencia que eso supone, nos va a permitir conocer mucho mejor cómo funciona el código que estamos escribiendo y además poder colaborar con el equipo de lenguajes y compiladores de Microsoft. Podemos hablar con los desarrolladores sobre nuevas características o mejoras del compilador a traves de Gitter para luego realizar pull requests.

Al recorrer el código encontraremos una estructura moderna, con tests unitarios, patrones, composición e inyección de dependencias usando MEF y muchos trucos de rendimiento para que el compilador y el IDE vayan muy fluidos. Una de las técnicas para mejorar el rendimiento es el uso de colecciones inmutables, que permiten la interacción con el código desde múltiples hilos sin necesidad de realizar bloqueos.

Novedades en Visual Studio 2015

Ahora que Roslyn es el compilador de C# y Visual Basic, Visual Studio 2015 aprovecha toda la potencia que le ofrecen los nuevos servicios del compilador para mejorar y añadir nuevas funcionalidades. Entre otras:

  • Refactorings en Visual Basic
  • Renombrado inteligente de variables, en vivo desde el editor
  • Nuevos refactorings como Introduce Local y Inline Temporary Variable
  • Avisos (lightbulbs) para saber dónde podemos mejorar nuestro código
  • Y muchas otras pequeñas mejoras como: coloreado de sintaxis en vista rápida, gestión más inteligente de nombres de variables en el Intellisense, etc…

Visual Basic Refactor

Mejorar la calidad del código

Uno de los puntos fuertes de Roslyn es que podemos incorporar herramientas de diagnóstico de código que se van a ejecutar dentro del compilador, tanto dentro de Visual Studio como en nuestros proyectos de forma individual. Esto quiere decir que podremos ejecutar los mismos diagnósticos desde la línea de comando y dentro de Visual Studio.

Las herramientas de diagnóstico, son librerías manejadas que se pueden distribuir como paquetes VSIX a nivel del IDE, como paquete de Nuget para realizar análisis a nivel de proyecto y también se pueden añadir manualmente como si se tratara de una referencia manual. Estas herramientas, en lugar de enlazarse con nuestro código, se ejecutarán dentro del compilador para comprobar si lo que está escrito cumple con las normas establecidas en ese diagnóstico. Cuando se detecte un fallo, el compilador nos proporcionará un aviso en forma de información, alerta o error que se mostrará en Visual Studio.

Aquí tenemos el caso de un código erróneo en un Worker Role de Azure, si compilamos este código no nos dará ningún error:

Bad Azure Async Code

Pero en realidad tiene uno bastante grave, en el método Run no se puede utilizar Async. Roslyn nos puede ayudar mucho en estos casos, pues podemos añadir a nuestro proyecto una herramienta de diagnóstico que nos avise de los errores que cometemos al utilizar una API:

image

Ahora si nos avisará del error grave que estamos cometiendo y evitará que el código compile, ahorrando muchos problemas a la hora de desplegar el código:

image

 

Un Diagnóstico con CodeFix

Además de poder utilizar diagnósticos predefinidos, o descargados a través de Nuget como en el caso anterior, vamos a poder crear nuestros propios diagnósticos y proporcionar con ellos correcciones que ayuden al desarrollador a mejorar el código. En el siguiente ejemplo tenemos un código que no cumple con las normas de calidad de un equipo que establecen que el código dentro de un bucle debe estar siempre entre llaves, de hecho el programador que ha escrito este código tampoco sabía tabular bien y nos ha dejado este código que puede llevar a confusión:

 string result = null;
for (int i = 0; i < 100; i++)
    result = Console.ReadLine();
    Console.WriteLine(result);

Para arreglar esta situación, vamos a crear un diagnóstico que busque dentro del árbol de sintaxis si la expresión que hay dentro de un bucle for está o no dentro de un bloque. Para ver qué elementos tenemos que buscar nos podemos ayudar de la herramienta Syntax Visualizer (en los enlaces del final del artículo está el instalador):

Roslyn Syntax Visualizer

Para ello, vamos a crear un nuevo proyecto de tipo Diagnostic with Code Fix (el enlace para instalar el SDK y la plantilla está al final del artículo):

Diagnostic with Code Fix

La plantilla nos genera un diagnóstico y una corrección de código de ejemplo, donde podremos darle un nombre, una categoría y en el fichero de recursos podremos añadir título, descripción y un mensaje. En la definición de la regla podremos indicar la severidad del diagnóstico: Información, Alerta o Error.

 internal static DiagnosticDescriptor Rule = 
    new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true);

 

En el diagnóstico vamos a sustituir el código de inicialización para que nos busque un nodo de sintaxis:

 public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(analyzeSyntax, SyntaxKind.ForStatement);
}

 

Y escribiremos el código del analizador para buscar dentro del nodo si el Statement no es de tipo Block, en ese caso será que el desarrollador no ha escrito llaves. Entonces llamaremos al método ReportDiagnostic para indicar que en el punto donde se encuentra la palabra clave hay algo que no es correcto:

 

 private void analyzeSyntax(SyntaxNodeAnalysisContext context)
{
    var forStatement = context.Node as ForStatementSyntax;
    if(forStatement?.Statement!=null && 
        !forStatement.Statement.IsKind(SyntaxKind.Block))
    {
        context.ReportDiagnostic(
            Diagnostic.Create(Rule, forStatement.ForKeyword.GetLocation(), 
                                forStatement.ForKeyword.Text));
    }
}

A partir de ahora, si intentamos compilar el código nos indicará dónde está el error y obtendremos un tooltip que nos explicará qué está pasando:

image

Ahora que ya sabemos encontrar el error, crearemos una correción que añada el bloque. Las soluciones a los diagnósticos siempre se componen de dos partes, el método que comprueba que se puede proporcionar una solución y el que se ejecuta para crear un nuevo árbol sintáctico. Es importante separarlos en esas dos etapas, para poder mantener un buen rendimiento en Visual Studio, ya que el código de RegisterCodeFixesAsync se ejecutará muchas veces mientras estemos utilizando el IDE.

 public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
    var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

    var diagnostic = context.Diagnostics.First();
    var diagnosticSpan = diagnostic.Location.SourceSpan;

    // Find the type declaration identified by the diagnostic.
    var forStatement = root.FindToken(diagnosticSpan.Start).Parent as ForStatementSyntax;

    // Register a code action that will invoke the fix.
    context.RegisterCodeFix(
        CodeAction.Create("Add block to code", c => addBlockAsync(context.Document, forStatement, c)),
        diagnostic);
}

La segunda parte del código es la que crea un nuevo árbol sintáctico, creando un bloque alrededor del Statement que ya tiene el bucle. Para ello haremos uso de la clase SyntaxFactory que nos proporciona muchos helpers para crear elementos de la sintaxis y algunos métodos With... que nos permiten ir encadenando acciones.

 private async Task addBlockAsync(Document document, ForStatementSyntax forStatement, CancellationToken c)
{
    var newBlock = SyntaxFactory.Block(forStatement.Statement);
    var newForStatement = forStatement.WithStatement(newBlock);
    var root = await document.GetSyntaxRootAsync(c);
    var newRoot=root.ReplaceNode(forStatement, newForStatement);
    return document.WithSyntaxRoot(newRoot);
}

Y al aparecernos el error ahora podremos también arreglar la situación:

codefix

 

Con estos pasos tan sencillos hemos hecho nuestro primer codefix. Como ejercicio/reto, probad a añadir otra comprobación igual para los demás tipos de bucle.

 

¿Qué necesito para usar Roslyn?

  • Visual Studio 2015: puedes descargar la preview actual (CTP6), ya incorpora Roslyn como compilador y ofrece todas las ventajas que incorpora el nuevo compilador.
  • Visual Studio 2015 CTP6 SDK: imprescindible para poder compilar los ejemplos
  • Plantillas de proyecto y extensiones, es importante que coincidan con la versión que hayamos descargado, en este caso CTP6: plantillasSyntax Visualizer

 

Juan Manuel Servera

Technical Evangelist – Frontend & IoT