Comparteix a través de


Analizadores de Roslyn y biblioteca compatible con código para ImmutableArrays

La Plataforma del compilador de .NET ("Roslyn") le ayuda a crear bibliotecas compatibles con código. Una biblioteca compatible con código proporciona funcionalidad que puede usar y herramientas (analizadores de Roslyn) para ayudarle a usar la biblioteca de la mejor manera o para evitar errores. En este tema se muestra cómo crear un analizador de Roslyn real para detectar errores comunes al usar el paquete NuGet System.Collections.Immutable . En el ejemplo también se muestra cómo proporcionar una corrección de código para un problema de código encontrado por el analizador. Los usuarios ven correcciones de código en la interfaz de usuario de bombilla de Visual Studio y pueden aplicar una corrección para el código automáticamente.

Introducción

Necesita lo siguiente para compilar este ejemplo:

  • Visual Studio 2015 (no una edición Express) o una versión posterior. Puede usar la edición gratuita de visual Studio Community Edition.
  • Visual Studio SDK. También puede, al instalar Visual Studio, comprobar Visual Studio Extensibility Tools en Herramientas comunes para instalar el SDK al mismo tiempo. Si ya ha instalado Visual Studio, también puede instalar este SDK; para ello, vaya al menú principal Archivo>nuevo>proyecto, elija C# en el panel de navegación izquierdo y, después, elija Extensibilidad. Al elegir la plantilla de proyecto "Install the Visual Studio Extensibility Tools" (Instalar las herramientas de extensibilidad de Visual Studio), se le pedirá que descargue e instale el SDK.
  • SDK de .NET Compiler Platform ("Roslyn"). También puede instalar este SDK; para ello, vaya al menú principal Archivo>nuevo>proyecto, elija C# en el panel de navegación izquierdo y, a continuación, elija Extensibilidad. Al elegir la plantilla de proyecto de ruta de navegación "Download the .NET Compiler Platform SDK" (Descargar el SDK de .NET Compiler Platform), se le pedirá que descargue e instale el SDK. Este SDK incluye el visualizador de sintaxis de Roslyn. Esta herramienta útil le ayuda a averiguar qué tipos de modelo de código debe buscar en el analizador. La infraestructura del analizador llama al código para tipos de modelo de código específicos, por lo que el código solo se ejecuta cuando es necesario y solo puede centrarse en analizar el código pertinente.

¿Cuál es el problema?

Imagine que proporciona una biblioteca con compatibilidad con ImmutableArray (por ejemplo, System.Collections.Immutable.ImmutableArray<T>). Los desarrolladores de C# tienen mucha experiencia con las matrices de .NET. Sin embargo, debido a la naturaleza de las técnicas de optimización y immutableArrays usadas en la implementación, las intuiciones del desarrollador de C# hacen que los usuarios de la biblioteca escriban código roto, como se explica a continuación. Además, los usuarios no ven sus errores hasta el tiempo de ejecución, que no es la experiencia de calidad que se usan en Visual Studio con .NET.

Los usuarios están familiarizados con la escritura de código como el siguiente:

var a1 = new int[0];
Console.WriteLine("a1.Length = {0}", a1.Length);
var a2 = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine("a2.Length = {0}", a2.Length);

La creación de matrices vacías para rellenar con las líneas de código posteriores y el uso de la sintaxis del inicializador de colección son familiares para los desarrolladores de C#. Sin embargo, escribir el mismo código para una immutableArray se bloquea en tiempo de ejecución:

var b1 = new ImmutableArray<int>();
Console.WriteLine("b1.Length = {0}", b1.Length);
var b2 = new ImmutableArray<int> { 1, 2, 3, 4, 5 };
Console.WriteLine("b2.Length = {0}", b2.Length);

El primer error se debe a que la implementación de ImmutableArray usa una estructura para encapsular el almacenamiento de datos subyacente. Las estructuras deben tener constructores sin parámetros para que default(T) las expresiones puedan devolver estructuras con todos los miembros cero o NULL. Cuando el código tiene b1.Lengthacceso a , hay un error de desreferenciación null en tiempo de ejecución porque no hay ninguna matriz de almacenamiento subyacente en la estructura ImmutableArray. La manera correcta de crear una immutableArray vacía es ImmutableArray<int>.Empty.

El error con inicializadores de colección se produce porque el ImmutableArray.Add método devuelve nuevas instancias cada vez que se llama. Dado que ImmutableArrays nunca cambia, cuando se agrega un nuevo elemento, se devuelve un nuevo objeto ImmutableArray (que puede compartir almacenamiento por motivos de rendimiento con una immutableArray existente previamente). Dado que b2 apunta a la primera ImmutableArray antes de llamar Add() a cinco veces, b2 es una immutableArray predeterminada. La llamada a Length en ella también se bloquea con un error de desreferencia null. La manera correcta de inicializar una immutableArray sin llamar manualmente a Add es usar ImmutableArray.CreateRange(new int[] {1, 2, 3, 4, 5}).

Búsqueda de tipos de nodo de sintaxis relevantes para desencadenar el analizador

Para empezar a compilar el analizador, primero averigó qué tipo de SyntaxNode necesita buscar. Inicie el Visualizador de sintaxis desde el menú Ver>otros visualizadores de sintaxis de Roslyn de Windows>.

Coloque el símbolo de intercalación del editor en la línea que declara b1. Verá que el visualizador de sintaxis muestra que está en un LocalDeclarationStatement nodo del árbol de sintaxis. Este nodo tiene un VariableDeclaration, que a su vez tiene un VariableDeclarator, que a su vez tiene EqualsValueClauseun , y finalmente hay un ObjectCreationExpression. Al hacer clic en el árbol del visualizador de sintaxis de nodos, la sintaxis de la ventana del editor resalta para mostrar el código representado por ese nodo. Los nombres de los subtipos SyntaxNode coinciden con los nombres usados en la gramática de C#.

Creación del proyecto del analizador

En el menú principal, elija Archivo>Nuevo>Proyecto. En el cuadro de diálogo Nuevo proyecto , en Proyectos de C# en la barra de navegación izquierda, elija Extensibilidad y, en el panel derecho, elija la plantilla de proyecto Analizador con corrección de código. Escriba un nombre y confirme el cuadro de diálogo.

La plantilla abre un archivo DiagnosticAnalyzer.cs . Elija esa pestaña del búfer del editor. Este archivo tiene una clase de analizador (formada a partir del nombre que asignó al proyecto) que deriva de (un tipo de DiagnosticAnalyzer API de Roslyn). La nueva clase tiene una DiagnosticAnalyzerAttribute declaración del analizador es relevante para el lenguaje C# para que el compilador detecte y cargue el analizador.

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ImmutableArrayAnalyzer : DiagnosticAnalyzer
{}

Puede implementar un analizador mediante Visual Basic que tenga como destino código de C# y viceversa. Es más importante en DiagnosticAnalyzerAttribute elegir si el analizador tiene como destino un idioma o ambos. Los analizadores más sofisticados que requieren modelado detallado del lenguaje solo pueden tener como destino un único lenguaje. Si el analizador, por ejemplo, solo comprueba los nombres de tipo o los nombres de miembros públicos, puede ser posible usar el modelo de lenguaje común que Roslyn ofrece en Visual Basic y C#. Por ejemplo, FxCop advierte que una clase implementa ISerializable, pero la clase no tiene el atributo es independiente del SerializableAttribute lenguaje y funciona para código de Visual Basic y C#.

Inicialización del analizador

Desplácese hacia abajo un poco en la DiagnosticAnalyzer clase para ver el Initialize método . El compilador llama a este método al activar un analizador. El método toma un AnalysisContext objeto que permite al analizador obtener información de contexto y registrar devoluciones de llamada para eventos para los tipos de código que desea analizar.

public override void Initialize(AnalysisContext context) {}

Abra una nueva línea en este método y escriba "context" para ver una lista de finalización de IntelliSense. Puede ver que en la lista de finalización hay muchos Register... métodos para controlar varios tipos de eventos. Por ejemplo, la primera, RegisterCodeBlockAction, llama de nuevo al código de un bloque, que normalmente es código entre llaves. El registro de un bloque también llama al código para el inicializador de un campo, el valor proporcionado a un atributo o el valor de un parámetro opcional.

Como otro ejemplo, RegisterCompilationStartAction, vuelve a llamar al código al principio de una compilación, lo que resulta útil cuando se necesita recopilar el estado en muchas ubicaciones. Puede crear una estructura de datos, por ejemplo, para recopilar todos los símbolos usados y cada vez que se llame al analizador para alguna sintaxis o símbolo, puede guardar información sobre cada ubicación de la estructura de datos. Cuando se le llama debido al final de la compilación, puede analizar todas las ubicaciones que guardó, por ejemplo, para informar de los símbolos que usa el código de cada using instrucción.

Con el Visualizador de sintaxis, ha aprendido a llamar a cuando el compilador procesa objectCreationExpression. Use este código para configurar la devolución de llamada:

context.RegisterSyntaxNodeAction(c => AnalyzeObjectCreation(c),
                                 SyntaxKind.ObjectCreationExpression);

Se registra para un nodo de sintaxis y se filtra solo para los nodos de sintaxis de creación de objetos. Por convención, los autores del analizador usan una expresión lambda al registrar acciones, lo que ayuda a mantener los analizadores sin estado. Puede usar la característica Generar a partir del uso de Visual Studio para crear el AnalyzeObjectCreation método . Esto también genera el tipo correcto de parámetro de contexto.

Establecimiento de propiedades para los usuarios del analizador

Para que el analizador aparezca correctamente en la interfaz de usuario de Visual Studio, busque y modifique la siguiente línea de código para identificar el analizador:

internal const string Category = "Naming";

Cambie "Naming" a "API Guidance".

A continuación, busque y abra el archivo Resources.resx en el proyecto mediante el Explorador de soluciones. Puede incluir una descripción para el analizador, el título, etc. Puede cambiar el valor de todos estos a "Don't use ImmutableArray<T> constructor" por ahora. Puede colocar argumentos de formato de cadena en la cadena ({0}, {1}, etc.) y versiones posteriores al llamar Diagnostic.Create()a , puede proporcionar una params matriz de argumentos que se van a pasar.

Análisis de una expresión de creación de objetos

El AnalyzeObjectCreation método toma un tipo de contexto diferente proporcionado por el marco del analizador de código. El Initialize método AnalysisContext permite registrar devoluciones de llamada de acción para configurar el analizador. Por SyntaxNodeAnalysisContextejemplo, tiene un CancellationToken objeto que puede pasar. Si un usuario empieza a escribir en el editor, Roslyn cancelará la ejecución de analizadores para guardar el trabajo y mejorar el rendimiento. Como otro ejemplo, este contexto tiene una propiedad Node que devuelve el nodo de sintaxis de creación de objetos.

Obtenga el nodo, que puede suponer que es el tipo para el que filtre la acción del nodo de sintaxis:

var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

Inicie Visual Studio con el analizador la primera vez.

Inicie Visual Studio mediante la compilación y ejecución del analizador (presione F5). Dado que el proyecto de inicio de la Explorador de soluciones es el proyecto VSIX, la ejecución del código compila el código y un VSIX y, a continuación, inicia Visual Studio con ese VSIX instalado. Al iniciar Visual Studio de esta manera, se inicia con un subárbol del registro distinto para que el uso principal de Visual Studio no se vea afectado por las instancias de prueba al compilar analizadores. La primera vez que se inicia de esta manera, Visual Studio realiza varias inicializaciones similares a cuando se inicia Visual Studio por primera vez después de instalarlo.

Cree un proyecto de consola y escriba el código de matriz en el método Main de las aplicaciones de consola:

var b1 = new ImmutableArray<int>();
Console.WriteLine("b1.Length = {0}", b1.Length);
var b2 = new ImmutableArray<int> { 1, 2, 3, 4, 5 };
Console.WriteLine("b2.Length = {0}", b2.Length);

Las líneas de código con ImmutableArray tienen subrayados ondulados porque necesita obtener el paquete NuGet inmutable y agregar una using instrucción al código. Presione el botón derecho del puntero en el nodo del proyecto en el Explorador de soluciones y elija Administrar paquetes NuGet. En el administrador de NuGet, escriba "Inmutable" en el cuadro de búsqueda y elija el elemento System.Collections.Immutable (no elija Microsoft.Bcl.Immutable) en el panel izquierdo y presione el botón Instalar en el panel derecho. Al instalar el paquete se agrega una referencia a las referencias del proyecto.

Sigue viendo subrayados ondulados rojos en ImmutableArray, así que coloque el símbolo de intercalación en ese identificador y presione Ctrl+. (punto) para abrir el menú de corrección sugerido y elija agregar la instrucción adecuada.using

Guarde Todo y cierre la segunda instancia de Visual Studio por ahora para ponerlo en un estado limpio para continuar.

Finalizar el analizador mediante editar y continuar

En la primera instancia de Visual Studio, establezca un punto de interrupción al principio del AnalyzeObjectCreation método presionando F9 con el símbolo de intercalación en la primera línea.

Vuelva a iniciar el analizador con F5 y, en la segunda instancia de Visual Studio, vuelva a abrir la aplicación de consola que creó la última vez.

Vuelva a la primera instancia de Visual Studio en el punto de interrupción porque el compilador de Roslyn vio una expresión de creación de objetos y llamó al analizador.

Obtenga el nodo de creación de objetos. Recorra la línea que establece la objectCreation variable presionando F10 y, en la ventana Inmediato, evalúe la expresión "objectCreation.ToString()". Verá que el nodo de sintaxis al que apunta la variable es el código "new ImmutableArray<int>()", solo lo que está buscando.

Obtiene el objeto ImmutableArray<T> Type. Debe comprobar si el tipo que se va a crear es ImmutableArray. En primer lugar, obtendrá el objeto que representa este tipo. Compruebe los tipos mediante el modelo semántico para asegurarse de que tiene exactamente el tipo correcto y no compara la cadena de ToString(). Escriba la siguiente línea de código al final de la función:

var immutableArrayOfTType =
    context.SemanticModel
           .Compilation
           .GetTypeByMetadataName("System.Collections.Immutable.ImmutableArray`1");

Los tipos genéricos se designan en metadatos con acentos inversas (') y el número de parámetros genéricos. Por eso no ves "... ImmutableArray<T>" en el nombre de los metadatos.

El modelo semántico tiene muchas cosas útiles que le permiten formular preguntas sobre símbolos, flujo de datos, duración variable, etc. Roslyn separa los nodos de sintaxis del modelo semántico por diversos motivos de ingeniería (rendimiento, modelado de código erróneo, etc.). Quiere que el modelo de compilación busque información contenida en referencias para obtener una comparación precisa.

Puede arrastrar el puntero de ejecución amarillo en el lado izquierdo de la ventana del editor. Arrástrelo hasta la línea que establece la variable y recorra la objectCreation nueva línea de código mediante F10. Si mantiene el puntero del mouse sobre la variable immutableArrayOfType, verá que encontramos el tipo exacto en el modelo semántico.

Obtiene el tipo de la expresión de creación de objetos. "Type" se usa de varias maneras en este artículo, pero esto significa que si tiene una expresión "foo nueva", debe obtener un modelo de Foo. Debe obtener el tipo de la expresión de creación de objetos para ver si es el tipo ImmutableArray<T> . Use de nuevo el modelo semántico para obtener información de símbolos del símbolo de tipo (ImmutableArray) en la expresión de creación de objetos. Escriba la siguiente línea de código al final de la función:

var symbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation.Type).Symbol as INamedTypeSymbol;

Dado que el analizador debe controlar el código incompleto o incorrecto en los búferes del editor (por ejemplo, hay una instrucción que falta using ), debe comprobar si symbolInfo es null. Debe obtener un tipo con nombre (INamedTypeSymbol) del objeto de información de símbolos para finalizar el análisis.

Compare los tipos. Dado que hay un tipo genérico abierto de T que estamos buscando y el tipo en el código es un tipo genérico concreto, se consulta la información de símbolos para lo que el tipo se construye a partir de (un tipo genérico abierto) y compara ese resultado con immutableArrayOfTType. Escriba lo siguiente al final del método :

if (symbolInfo != null &&
    symbolInfo.ConstructedFrom.Equals(immutableArrayOfTType))
{}

Informe del diagnóstico. Notificar el diagnóstico es bastante fácil. Use la regla creada automáticamente en la plantilla de proyecto, que se define antes del método Initialize. Dado que esta situación en el código es un error, puede cambiar la línea que inicializó Regla para reemplazar DiagnosticSeverity.Warning (subrayado ondulado verde) por DiagnosticSeverity.Error (subrayado ondulado rojo). El resto de la regla se inicializa desde los recursos que editó cerca del principio del tutorial. También debe notificar la ubicación del subrayado ondulado, que es la ubicación de la especificación de tipo de la expresión de creación de objetos. Escriba este código en el if bloque :

context.ReportDiagnostic(Diagnostic.Create(Rule, objectCreation.Type.GetLocation()));

La función debe tener un aspecto similar al siguiente (quizás formateado de forma diferente):

private void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context)
{
    var objectCreation = (ObjectCreationExpressionSyntax)context.Node;
    var immutableArrayOfTType =
        context.SemanticModel
               .Compilation
               .GetTypeByMetadataName(
                   "System.Collections.Immutable.ImmutableArray`1");
    var symbolInfo = context.SemanticModel.GetSymbolInfo(objectCreation.Type).Symbol as
        INamedTypeSymbol;
    if (symbolInfo != null &&
        symbolInfo.ConstructedFrom.Equals(immutableArrayOfTType))
    {
        context.ReportDiagnostic(
            Diagnostic.Create(Rule, objectCreation.Type.GetLocation()));
    }
}

Quite el punto de interrupción para que pueda ver que el analizador funciona (y deje de volver a la primera instancia de Visual Studio). Arrastre el puntero de ejecución al principio del método y presione F5 para continuar con la ejecución. Al volver a la segunda instancia de Visual Studio, el compilador comenzará a examinar el código de nuevo y llamará al analizador. Puede ver un subrayado ondulado bajo ImmutableType<int>.

Agregar una "corrección de código" para el problema de código

Antes de comenzar, cierre la segunda instancia de Visual Studio y detenga la depuración en la primera instancia de Visual Studio (donde está desarrollando el analizador).

Agregue una nueva clase. Use el menú contextual (botón derecho del puntero) en el nodo del proyecto en el Explorador de soluciones y elija agregar un nuevo elemento. Agregue una clase denominada BuildCodeFixProvider. Esta clase debe derivar de CodeFixProvidery tendrá que usar Ctrl+. (punto) para invocar la corrección de código que agrega la instrucción correcta.using Esta clase también debe anotarse con ExportCodeFixProvider el atributo y deberá agregar una using instrucción para resolver la LanguageNames enumeración. Debe tener un archivo de clase con el código siguiente en él:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;

namespace ImmutableArrayAnalyzer
{
    [ExportCodeFixProvider(LanguageNames.CSharp)]
    class BuildCodeFixProvider : CodeFixProvider
    {}

Miembros derivados del código auxiliar. Ahora, coloque el símbolo de intercalación del editor en el identificador CodeFixProvider y presione Ctrl+. (punto) para extraer el código auxiliar de la implementación de esta clase base abstracta. Esto genera una propiedad y un método automáticamente.

Implemente la propiedad . Rellene el cuerpo de get la FixableDiagnosticIds propiedad con el código siguiente:

return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);

Roslyn reúne diagnósticos y correcciones mediante la coincidencia de estos identificadores, que son solo cadenas. La plantilla de proyecto generó un identificador de diagnóstico y puede cambiarlo. El código de la propiedad simplemente devuelve el identificador de la clase analyzer.

El método RegisterCodeFixAsync toma un contexto. El contexto es importante porque una corrección de código se puede aplicar a varios diagnósticos o podría haber más de un problema en una línea de código. Si escribe "context". en el cuerpo del método, la lista de finalización de IntelliSense le mostrará algunos miembros útiles. Hay un miembro CancellationToken que puede comprobar si algo quiere cancelar la corrección. Hay un miembro document que tiene muchos miembros útiles y le permite acceder a los objetos del modelo de proyecto y solución. Hay un miembro Span que es el inicio y el final de la ubicación de código especificada al notificar el diagnóstico.

Haga que el método sea asincrónico. Lo primero que debe hacer es corregir la declaración del método generado para que sea un async método. La corrección de código para aplicar código auxiliar a la implementación de una clase abstracta no incluye la async palabra clave aunque el método devuelva un Task.

Obtenga la raíz del árbol de sintaxis. Para modificar el código, debe generar un nuevo árbol de sintaxis con los cambios que realiza la corrección de código. Necesita desde Document el contexto para llamar a GetSyntaxRootAsync. Este es un método asincrónico porque hay un trabajo desconocido para obtener el árbol de sintaxis, posiblemente, obtener el archivo del disco, analizarlo y compilar el modelo de código de Roslyn para él. La interfaz de usuario de Visual Studio debe responder durante este tiempo, que usa async las habilitaciones. Reemplace la línea de código del método por lo siguiente:

var root = await context.Document
                        .GetSyntaxRootAsync(context.CancellationToken);

Busque el nodo con el problema. Pase el intervalo del contexto, pero es posible que el nodo que encuentre no sea el código que tiene que cambiar. El diagnóstico notificado solo proporcionó el intervalo para el identificador de tipo (donde pertenecía el subrayado ondulado), pero debe reemplazar toda la expresión de creación de objetos, incluida la new palabra clave al principio y los paréntesis al final. Agregue el código siguiente al método (y use Ctrl+. para agregar una using instrucción para ):ObjectCreationExpressionSyntax

var objectCreation = root.FindNode(context.Span)
                         .FirstAncestorOrSelf<ObjectCreationExpressionSyntax>();

Registre la corrección de código para la interfaz de usuario de bombilla. Al registrar la corrección de código, Roslyn se conecta automáticamente a la interfaz de usuario de bombilla de Visual Studio. Los usuarios finales verán que pueden usar Ctrl+. (punto) cuando el analizador desactiva un ImmutableArray<T> uso incorrecto del constructor. Dado que el proveedor de corrección de código solo se ejecuta cuando hay un problema, puede suponer que tiene la expresión de creación de objetos que estaba buscando. Desde el parámetro de contexto, puede registrar la nueva corrección de código agregando el código siguiente al final del RegisterCodeFixAsync método:

context.RegisterCodeFix(
            CodeAction.Create("Use ImmutableArray<T>.Empty",
                              c => ChangeToImmutableArrayEmpty(objectCreation,
                                                               context.Document,
                                                               c)),
            context.Diagnostics[0]);

Debe colocar el símbolo de intercalación del editor en el identificador y, a continuación, CodeActionusar Ctrl+. (punto) para agregar la instrucción adecuada using para este tipo.

A continuación, coloque el símbolo de intercalación del editor en el ChangeToImmutableArrayEmpty identificador y use Ctrl+. De nuevo para generar el código auxiliar de este método.

Este último fragmento de código que agregó registra la corrección de código pasando un CodeAction y el identificador de diagnóstico para el tipo de problema encontrado. En este ejemplo, solo hay un identificador de diagnóstico para el que este código proporciona correcciones, por lo que solo puede pasar el primer elemento de la matriz de identificadores de diagnóstico. Al crear , CodeActionse pasa el texto que la interfaz de usuario de bombilla debe usar como descripción de la corrección de código. También se pasa una función que toma cancellationToken y devuelve un nuevo documento. El nuevo documento tiene un nuevo árbol de sintaxis que incluye el código revisado que llama a ImmutableArray.Empty. Este fragmento de código usa una expresión lambda para que pueda cerrarse sobre el nodo objectCreation y el documento del contexto.

Construya el nuevo árbol de sintaxis. En el ChangeToImmutableArrayEmpty método cuyo código auxiliar generó anteriormente, escriba la línea de código: ImmutableArray<int>.Empty;. Si vuelve a ver la ventana de herramientas Del visualizador de sintaxis, puede ver que esta sintaxis es un nodo SimpleMemberAccessExpression. Eso es lo que este método necesita para construir y devolver en un nuevo documento.

El primer cambio a ChangeToImmutableArrayEmpty es agregar async antes Task<Document> porque los generadores de código no pueden suponer que el método debe ser asincrónico.

Rellene el cuerpo con el código siguiente para que el método tenga un aspecto similar al siguiente:

private async Task<Document> ChangeToImmutableArrayEmpty(
    ObjectCreationExpressionSyntax objectCreation, Document document,
    CancellationToken c)
{
    var generator = SyntaxGenerator.GetGenerator(document);
    var memberAccess =
        generator.MemberAccessExpression(objectCreation.Type, "Empty");
    var oldRoot = await document.GetSyntaxRootAsync(c);
    var newRoot = oldRoot.ReplaceNode(objectCreation, memberAccess);
    return document.WithSyntaxRoot(newRoot);
}

Tendrá que colocar el símbolo de intercalación del editor en el SyntaxGenerator identificador y usar Ctrl+. (punto) para agregar la instrucción adecuada using para este tipo.

Este código usa SyntaxGenerator, que es un tipo útil para construir código nuevo. Después de obtener un generador para el documento que tiene el problema de código, ChangeToImmutableArrayEmpty llama a MemberAccessExpression, pasando el tipo que tiene el miembro al que queremos tener acceso y pasando el nombre del miembro como una cadena.

A continuación, el método captura la raíz del documento y, dado que esto puede implicar trabajo arbitrario en el caso general, el código espera esta llamada y pasa el token de cancelación. Los modelos de código roslyn son inmutables, como trabajar con una cadena de .NET; cuando se actualiza la cadena, se obtiene un nuevo objeto de cadena a cambio. Cuando se llama a ReplaceNode, se devuelve un nuevo nodo raíz. La mayoría del árbol de sintaxis se comparte (porque es inmutable), pero el objectCreation nodo se reemplaza por el memberAccess nodo, así como todos los nodos primarios hasta la raíz del árbol de sintaxis.

Prueba de la corrección de código

Ahora puede presionar F5 para ejecutar el analizador en una segunda instancia de Visual Studio. Abra el proyecto de consola que usó antes. Ahora debería ver que la bombilla aparece donde la nueva expresión de creación de objetos es para ImmutableArray<int>. Si presiona Ctrl+. (punto), verá la corrección del código y verá una vista previa de diferencias de código generada automáticamente en la interfaz de usuario de bombilla. Roslyn crea esto para ti.

Sugerencia pro: si inicia la segunda instancia de Visual Studio y no ve la bombilla con la corrección de código, es posible que tenga que borrar la caché de componentes de Visual Studio. Al borrar la memoria caché, Visual Studio obliga a volver a examinar los componentes, por lo que Visual Studio debe seleccionar el componente más reciente. En primer lugar, apague la segunda instancia de Visual Studio. A continuación, en el Explorador de Windows, vaya a %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. (La versión "16.0" cambia de la versión a la versión con Visual Studio). Elimine el subdirectorio ComponentModelCache.

Vídeo de conversación y finalización del proyecto de código

Puede ver todo el código terminado aquí. Las subcarpetas DoNotUseImmutableArrayCollectionInitializer y DoNotUseImmutableArrayCtor tienen cada uno un archivo C# para buscar problemas y un archivo de C# que implementa las correcciones de código que se muestran en la interfaz de usuario de bombilla de Visual Studio. Tenga en cuenta que el código terminado tiene un poco más de abstracción para evitar capturar el objeto de tipo ImmutableArray<T> sobre y más. Usa acciones registradas anidadas para guardar el objeto de tipo en un contexto que esté disponible cada vez que se ejecuten las sub acciones (analizar la creación de objetos y analizar inicializaciones de colección).