Compartilhar via


Analisadores do Roslyn e biblioteca com reconhecimento de código para ImmutableArrays

A Plataforma do Compilador .NET ("Roslyn") ajuda você a criar bibliotecas conscientes do código. Uma biblioteca com reconhecimento de código fornece funcionalidades que você pode usar e ferramentas (analisadores Roslyn) para ajudá-lo a usar a biblioteca da melhor maneira ou para evitar erros. Este tópico mostra como criar um analisador Roslyn do mundo real para detectar erros comuns ao usar o pacote System.Collections.Immutable NuGet. O exemplo também demonstra como fornecer uma correção de código para um problema de código encontrado pelo analisador. Os usuários veem correções de código na interface do usuário da lâmpada do Visual Studio e podem aplicar uma correção para o código automaticamente.

Introdução

Você precisa do seguinte para criar este exemplo:

  • Visual Studio 2015 (não um Express Edition) ou uma versão posterior. Você pode usar a edição gratuita Visual Studio Community
  • SDK do Visual Studio. Você também pode, ao instalar o Visual Studio, marcar Ferramentas de Extensibilidade do Visual Studio em Ferramentas Comuns para instalar o SDK ao mesmo tempo. Se você já tiver instalado o Visual Studio, também poderá instalar esse SDK acessando o menu principal Arquivo>Novo>Projeto, escolhendo C# no painel de navegação esquerdo e, em seguida, escolhendo Extensibilidade. Quando você escolhe o modelo de projeto "Instalar as Ferramentas de Extensibilidade do Visual Studio", ele solicita que você baixe e instale o SDK.
  • SDK do .NET Compiler Platform ("Roslyn"). Você também pode instalar esse SDK acessando o menu principal Arquivo>Novo>Projeto, escolhendo C# no painel de navegação esquerdo e escolhendo Extensibilidade. Quando você escolhe "Baixar o modelo de projeto do SDK do .NET Compiler Platform", ele solicita que você baixe e instale o SDK. Este SDK inclui o Visualizador de Sintaxe Roslyn . Essa ferramenta útil ajuda você a descobrir quais tipos de modelo de código você deve procurar em seu analisador. A infraestrutura do analisador chama seu código para tipos de modelo de código específicos, portanto, seu código só é executado quando necessário e pode se concentrar apenas na análise de código relevante.

Qual é o problema?

Imagine que você forneça uma biblioteca com suporte a ImmutableArray (por exemplo, System.Collections.Immutable.ImmutableArray<T>). Os desenvolvedores do C# têm muita experiência com matrizes .NET. No entanto, devido à natureza dos Arrays Imutáveis e às técnicas de otimização usadas na implementação, as intuições dos desenvolvedores de C# fazem com que os usuários de sua biblioteca escrevam código com falhas, conforme explicado abaixo. Além disso, os usuários não veem seus erros até o tempo de execução, que não é a experiência de qualidade à qual estão acostumados no Visual Studio com o .NET.

Os usuários estão familiarizados com a escrita de código como o seguinte:

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);

A criação de matrizes vazias para preencher com linhas de código subsequentes e usar a sintaxe do inicializador de coleção é familiar para desenvolvedores C#. No entanto, escrever o mesmo código para um ImmutableArray gera um erro em tempo de execução.

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);

O primeiro erro ocorre devido à implementação ImmutableArray usando um struct para encapsular o armazenamento de dados subjacente. Estruturas devem ter construtores sem parâmetro para que as expressões default(T) possam retornar estruturas com todos os membros igual a zero ou nulos. Quando o código acessa b1.Length, há um erro de desreferência nulo em tempo de execução porque não há nenhuma matriz de armazenamento subjacente no struct ImmutableArray. A maneira correta de criar um ImmutableArray vazio é ImmutableArray<int>.Empty.

O erro com inicializadores de coleção ocorre porque o método ImmutableArray.Add retorna novas instâncias sempre que você o chama. Como ImmutableArrays nunca é alterado, quando você adiciona um novo elemento, você obtém de volta um novo objeto ImmutableArray (que pode compartilhar o armazenamento por motivos de desempenho com um ImmutableArray anteriormente existente). Como b2 aponta para o primeiro ImmutableArray antes de chamar Add() cinco vezes, b2 é um ImmutableArray padrão. O Comprimento da Chamada também falha com um erro de desreferenciamento nulo. A maneira correta de inicializar um ImmutableArray sem chamar manualmente Add é usar ImmutableArray.CreateRange(new int[] {1, 2, 3, 4, 5}).

Encontre tipos relevantes de nó de sintaxe para disparar o seu analisador

Para começar a criar o analisador, primeiro descubra que tipo de SyntaxNode você precisa procurar. Inicie o Syntax Visualizer do menu Exibir>Outras Janelas>Roslyn Syntax Visualizer.

Coloque o cursor do editor na linha que declara b1. Você verá que o Visualizador de Sintaxe mostra que você está em um nó LocalDeclarationStatement da árvore de sintaxe. Esse nó tem um VariableDeclaration, que por sua vez tem um VariableDeclarator, que por sua vez tem um EqualsValueClause e, por fim, há um ObjectCreationExpression. Quando você clica na árvore de nós do Syntax Visualizer, a sintaxe na janela do editor se destaca para mostrar o código representado por esse nó. Os nomes dos subtipos SyntaxNode correspondem aos nomes usados na gramática C#.

Criar o projeto do analisador

No menu principal, escolha Arquivo>Novo>Projeto. Na caixa de diálogo Novo Projeto, em projetos C# na barra de navegação à esquerda, escolha Extensibilidade e, no painel à direita, escolha o modelo de projeto Analisador com Correção de Código. Insira um nome e confirme a caixa de diálogo.

O modelo abre um arquivo DiagnosticAnalyzer.cs. Escolha a guia buffer do editor. Esse arquivo tem uma classe de analisador (formada a partir do nome que você deu ao projeto) que deriva de DiagnosticAnalyzer (um tipo de API Roslyn). Sua nova classe tem um DiagnosticAnalyzerAttribute declarando que o analisador é relevante para a linguagem C# para que o compilador descubra e carregue o analisador.

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

Você pode implementar um analisador usando o Visual Basic direcionado ao código C# e vice-versa. É mais importante no DiagnosticAnalyzerAttribute escolher se o analisador tem como destino um idioma ou ambos. Analisadores mais sofisticados que exigem modelagem detalhada do idioma só podem ter como destino um único idioma. Se o seu analisador, por exemplo, checar apenas nomes de tipo ou nomes de membros públicos, talvez seja possível usar o modelo de linguagem comum que o Roslyn oferece no Visual Basic e no C#. Por exemplo, o FxCop adverte que uma classe implementa ISerializable, mas a classe não tem o atributo SerializableAttribute é independente de linguagem e funciona para código Visual Basic e C#.

Inicializar o analisador

Role para baixo um pouco na classe DiagnosticAnalyzer para ver o método Initialize. O compilador chama esse método ao ativar um analisador. O método usa um objeto AnalysisContext que permite que o analisador obtenha informações de contexto e registre retornos de chamada para eventos para os tipos de código que você deseja analisar.

public override void Initialize(AnalysisContext context) {}

Abra uma nova linha neste método e digite "context" para ver uma lista de conclusão do IntelliSense. Você pode ver na lista de conclusão que há muitos métodos Register... para lidar com vários tipos de eventos. Por exemplo, a primeira, RegisterCodeBlockAction, realiza uma chamada de volta para seu código buscando um bloco, que geralmente é o código entre chaves. O registro de um bloco também chama de volta seu código buscando o inicializador de um campo, o valor fornecido a um atributo ou o valor de um parâmetro opcional.

Como outro exemplo, RegisterCompilationStartActionretorna ao seu código no início de uma compilação, o que é útil quando você precisa coletar o estado em vários locais. Você pode criar uma estrutura de dados, digamos, para coletar todos os símbolos usados e sempre que o analisador for chamado novamente para alguma sintaxe ou símbolo, você poderá salvar informações sobre cada local em sua estrutura de dados. Quando você é chamado de volta devido ao término da compilação, pode analisar todos os locais salvos, por exemplo, para relatar quais símbolos o código usa de cada instrução using.

Usando o Visualizador de Sintaxe, você aprendeu que quer ser notificado quando o compilador processa um ObjectCreationExpression. Use este código para configurar o retorno de chamada:

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

Você se inscreve para um nó de sintaxe e filtra apenas os nós de sintaxe de criação de objeto. Por convenção, os autores do analisador usam uma expressão lambda na hora de registrar ações, o que ajuda a manter os analisadores sem estado. Você pode usar o recurso do Visual Studio Gerar do Uso para criar o método AnalyzeObjectCreation. Isso também gera o tipo correto de parâmetro de contexto para você.

Definir propriedades para usuários do analisador

Para que o analisador apareça na interface do usuário do Visual Studio adequadamente, procure e modifique a seguinte linha de código para identificar o analisador:

internal const string Category = "Naming";

Altere "Naming" para "API Guidance".

Em seguida, localize e abra o arquivo Resources.resx em seu projeto usando o Gerenciador de Soluções. Você pode colocar uma descrição para seu analisador, título etc. e pode alterar o valor de todos eles para "Don't use ImmutableArray<T> constructor" por enquanto. Você pode colocar argumentos de formatação de cadeia de caracteres em sua cadeia de caracteres ({0}, {1}, etc.) e, posteriormente, ao chamar Diagnostic.Create(), você pode fornecer uma matriz params de argumentos a serem passados.

Analisar uma expressão de criação de objeto

O método AnalyzeObjectCreation usa um tipo diferente de contexto fornecido pela estrutura do analisador de código. O Initialize do método AnalysisContext permite registrar retornos de chamada de ação para configurar o analisador. O SyntaxNodeAnalysisContext, por exemplo, tem um CancellationToken que você pode passar. Se um usuário começar a digitar no editor, Roslyn cancelará a execução de analisadores para salvar o trabalho e melhorar o desempenho. Como outro exemplo, esse contexto tem uma propriedade Node que retorna o nó de sintaxe de criação de objeto.

Obtenha o nó, que você pode supor ser o tipo para o qual você filtrou a ação do nó de sintaxe:

var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

Inicie o Visual Studio com o analisador pela primeira vez

Inicie o Visual Studio criando e executando o analisador (pressione F5). Como o projeto de inicialização no Gerenciador de Soluções é o projeto VSIX, a execução do código cria seu código e um VSIX e, em seguida, inicia o Visual Studio com esse VSIX instalado. Quando você inicia o Visual Studio dessa forma, ele é iniciado com um hive de registro distinto para que o uso principal do Visual Studio não seja afetado pelas instâncias de teste durante a criação de analisadores. Na primeira vez que você inicia dessa forma, o Visual Studio faz várias inicializações semelhantes a quando você iniciou o Visual Studio pela primeira vez depois de instalá-lo.

Crie um projeto de console e insira o código da matriz em seus aplicativos de console Método principal:

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);

As linhas de código com ImmutableArray têm linhas onduladas porque você precisa obter o pacote NuGet imutável e adicionar uma instrução using ao seu código. Pressione o botão de ponteiro para a direita no nó do projeto no Gerenciador de Soluções e escolha Gerenciar Pacotes NuGet. No gerenciador do NuGet, digite "Imutável" na caixa de pesquisa e escolha o item System.Collections.Immutable (não escolha Microsoft.Bcl.Immutable) no painel esquerdo e pressione o botão Instalar no painel direito. A instalação do pacote adiciona uma referência às referências do projeto.

Você ainda vê linhas onduladas vermelhas em ImmutableArray, portanto, coloque o cursor nesse identificador e pressione Ctrl+. (ponto) para abrir o menu de correção sugerido e optar por adicionar a instrução using apropriada.

Salve tudo e feche a segunda instância do Visual Studio para que você fique em um estado limpo para continuar.

Conclua o analisador usando a função editar e continuar.

Na primeira instância do Visual Studio, defina um ponto de interrupção no início do método AnalyzeObjectCreation pressionando F9 com o cursor na primeira linha.

Inicie o analisador novamente com F5e, na segunda instância do Visual Studio, reabra o aplicativo de console criado da última vez.

Você retorna à primeira instância do Visual Studio no ponto de interrupção porque o compilador Roslyn identificou uma expressão de criação de objeto e chamou o seu analisador.

Obtenha o nó de criação do objeto. Percorra a linha que define a variável objectCreation pressionando F10e avalie a expressão na Janela Imediata "objectCreation.ToString()". Você verá que o nó de sintaxe ao qual a variável aponta é o código "new ImmutableArray<int>()", exatamente o que você está procurando.

Obtenha um objeto do tipo ImmutableArray<T>. Você precisa verificar se o tipo que está sendo criado é ImmutableArray. Primeiro, você obtém o objeto que representa esse tipo. Você verifica os tipos usando o modelo semântico para garantir que você tenha exatamente o tipo certo e não compare a cadeia de caracteres de ToString(). Insira a seguinte linha de código no final da função:

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

Você atribui tipos genéricos em metadados com acento grave (`) e o número de parâmetros genéricos. É por isso que você não vê "... ImmutableArray<T>" no nome dos metadados.

O modelo semântico tem muitas coisas úteis que permitem que você faça perguntas sobre símbolos, fluxo de dados, tempo de vida variável etc. Roslyn separa nós de sintaxe do modelo semântico por vários motivos de engenharia (desempenho, modelagem de código incorreto etc.). Você deseja que o modelo de compilação procure informações contidas em referências para comparação precisa.

Você pode arrastar o ponteiro de execução amarelo no lado esquerdo da janela do editor. Arraste-o até a linha que define a variável objectCreation e avance sobre sua nova linha de código usando F10. Se você passar o ponteiro do mouse sobre a variável immutableArrayOfType, verá que encontramos o tipo exato no modelo semântico.

Obtenha o tipo da expressão de criação de objeto. "Type" é usado de algumas maneiras neste artigo, mas isso significa que, se você tiver a expressão "new Foo", precisará obter um modelo de Foo. Você precisa obter o tipo da expressão de criação de objeto para ver se é do tipo ImmutableArray<T>. Use o modelo semântico novamente para obter informações de símbolo para o símbolo de tipo (ImmutableArray) na expressão de criação do objeto. Insira a seguinte linha de código no final da função:

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

Como o analisador precisa lidar com código incompleto ou incorreto nos buffers do editor (por exemplo, há uma instrução using faltando), você deve verificar se symbolInfo está sendo null. Você precisa obter um tipo nomeado (INamedTypeSymbol) a partir do objeto de informações de símbolo para concluir a análise.

Compare os tipos. Como há um tipo genérico aberto de T que estamos procurando e o tipo no código é um tipo genérico concreto, você consulta as informações sobre o símbolo de qual tipo está sendo construído (um tipo genérico aberto) e compara esse resultado com immutableArrayOfTType. Insira o seguinte no final do método:

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

Relatar o diagnóstico. Relatar o diagnóstico é muito fácil. Use a Regra criada para você no modelo de projeto, que é definido antes do método Initialize. Como essa situação no código é um erro, você pode alterar a linha que inicializou a Regra para substituir DiagnosticSeverity.Warning (rabiscos verdes) por DiagnosticSeverity.Error (rabiscos vermelhos). O restante da regra é inicializado a partir dos recursos que você editou perto do início do passo a passo. Você também precisa relatar o local das linhas onduladas, que é o local da especificação de tipo da expressão de criação do objeto. Insira este código no bloco if:

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

Sua função deve ter esta aparência (talvez formatada 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()));
    }
}

Remova o ponto de interrupção para que você possa ver o analisador funcionando (e pare de retornar à primeira instância do Visual Studio). Arraste o ponteiro de execução até o início do método e pressione F5 para continuar a execução. Quando você voltar para a segunda instância do Visual Studio, o compilador começará a examinar o código novamente e ele chamará o analisador. Você pode ver uma linha ondulada em ImmutableType<int>.

Adicionando uma "correção de código" para o problema de código

Antes de começar, feche a segunda instância do Visual Studio e interrompa a depuração na primeira instância do Visual Studio (em que você está desenvolvendo o analisador).

Adicione uma nova classe. Use o menu de atalho (botão de ponteiro direito) no nó do projeto no Gerenciador de Soluções e escolha para adicionar um novo item. Adicionar uma classe chamada BuildCodeFixProvider. Essa classe precisa derivar de CodeFixProvider, e você precisará usar Ctrl+. (ponto) para invocar a correção de código que adiciona a instrução using correta. Essa classe também precisa ser anotada com o atributo ExportCodeFixProvider, e você precisará adicionar uma instrução using para resolver a enumeração LanguageNames. Você deve ter um arquivo de classe com o seguinte código:

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

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

Substitua os membros derivados por stubs. Agora, coloque o cursor do editor no identificador CodeFixProvider e pressione Ctrl+. (ponto) para descartar a implementação dessa classe base abstrata. Isso gera uma propriedade e um método para você.

Implemente a propriedade. Preencha o corpo FixableDiagnosticIds da propriedade get com o seguinte código:

return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);

Roslyn reúne diagnósticos e correções ao associar esses identificadores, que são apenas simples cadeias de texto. O modelo de projeto gerou uma ID de diagnóstico para você e você está livre para alterá-la. O código na propriedade retorna apenas a ID da classe do analisador.

O método RegisterCodeFixAsync usa um contexto. O contexto é importante porque uma correção de código pode se aplicar a vários diagnósticos ou pode haver mais de um problema em uma linha de código. Se você digitar "contexto" no corpo do método, a lista de conclusão do IntelliSense mostrará alguns membros úteis. Há um membro CancellationToken que você pode verificar para ver se algo deseja cancelar a correção. Há um membro Document que possui muitos membros úteis e permite que você acesse os objetos de modelo do projeto e da solução. Há um membro Span que é o início e o fim do local do código especificado quando você relatou o diagnóstico.

Faça com que o método seja assíncrono. A primeira coisa que você precisa fazer é corrigir a declaração do método gerado para ser um método async. A correção de código para remover a implementação de uma classe abstrata não inclui a palavra-chave async mesmo que o método retorne um Task.

Obtenha a raiz da árvore de sintaxe. Para modificar o código, você precisa produzir uma nova árvore de sintaxe com as alterações feitas pela correção de código. Você precisa do Document do contexto para chamar GetSyntaxRootAsync. Esse é um método assíncrono porque há um trabalho desconhecido para obter a árvore de sintaxe, possivelmente incluindo obter o arquivo do disco, analisá-lo e criar o modelo de código Roslyn para ele. A interface de usuário do Visual Studio deve ser responsiva durante esse tempo, o que é habilitado pelo uso de async. Substitua a linha de código no método pelo seguinte:

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

Localize o nó com o problema. Você passa o intervalo do contexto, mas o nó encontrado pode não ser o código que você precisa alterar. O diagnóstico informado forneceu apenas o intervalo para o identificador de tipo (onde a linha ondulada pertencia), mas é necessário substituir toda a expressão de criação de objeto, incluindo a palavra-chave new no início e os parênteses no final. Adicione o seguinte código ao método (e use Ctrl+. para adicionar uma instrução using para ObjectCreationExpressionSyntax):

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

Registre sua correção de código-fonte para a interface da lâmpada. Quando você registra sua correção de código, o Roslyn conecta-se automaticamente à interface do usuário da lâmpada do Visual Studio. Os usuários finais verão que podem usar Ctrl+. (ponto) quando o analisador sublinha com linha ondulada um uso de construtor ImmutableArray<T> inválido. Como o provedor de correção de código só é executado quando há um problema, você pode assumir que tem a expressão de criação do objeto que você estava procurando. No parâmetro de contexto, você pode registrar a nova correção de código adicionando o seguinte código ao final de RegisterCodeFixAsync método:

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

Você precisa colocar o cursor do editor no identificador CodeAction e, em seguida, usar ctrl+. (ponto) para adicionar a instrução using apropriada para esse tipo.

Em seguida, coloque o cursor do editor no identificador ChangeToImmutableArrayEmpty e use Ctrl+. novamente para gerar este stub de método para você.

Este último snippet de código adicionado registra a correção de código passando um CodeAction e a ID de diagnóstico para o tipo de problema encontrado. Neste exemplo, há apenas uma ID de diagnóstico para a qual esse código fornece correções, para que você possa simplesmente passar o primeiro elemento da matriz de IDs de diagnóstico. Ao criar o CodeAction, você passa o texto que a interface do usuário da lâmpada deve usar como uma descrição da correção de código. Você também passa uma função que usa CancellationToken e retorna um novo Documento. O novo Documento tem uma nova árvore de sintaxe que inclui o código corrigido que chama ImmutableArray.Empty. Esse trecho de código usa uma função lambda para capturar o nó objectCreation e o Documento do contexto.

Construa a nova árvore de sintaxe. No método ChangeToImmutableArrayEmpty cujo stub você gerou anteriormente, insira a linha de código: ImmutableArray<int>.Empty;. Se você exibir a janela da ferramenta Syntax Visualizer novamente, poderá ver que essa sintaxe é um nó SimpleMemberAccessExpression. É isso que este método precisa construir e retornar em um novo Documento.

A primeira alteração para ChangeToImmutableArrayEmpty é adicionar async antes de Task<Document> porque os geradores de código não podem assumir que o método deve ser assíncrono.

Preencha o corpo com o seguinte código para que seu método fique parecido com este:

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);
}

Você precisará colocar o cursor do editor no identificador SyntaxGenerator e usar Ctrl+. (ponto) para adicionar a instrução using apropriada para esse tipo.

Esse código usa SyntaxGenerator, que é um tipo útil para construir um novo código. Depois de obter um gerador para o documento que tem o problema de código, ChangeToImmutableArrayEmpty chama MemberAccessExpression, especificando o tipo que contém o membro que desejamos acessar e fornecendo o nome do membro como uma cadeia de caracteres.

Em seguida, o método busca a raiz do documento e, como isso pode envolver trabalho arbitrário no caso geral, o código aguarda essa chamada e passa o token de cancelamento. Modelos de código do Roslyn são imutáveis, como trabalhar com uma string .NET; quando você atualiza a string, obtém um novo objeto de string como resultado. Quando você chama ReplaceNode, você recebe de volta um novo nó raiz. A maior parte da árvore sintática é compartilhada (porque é imutável), mas o nó objectCreation é substituído pelo nó memberAccess, bem como todos os nós ascendentes até a raiz da árvore sintática.

Experimente a sua correção de código

Agora você pode pressionar F5 para executar o analisador em uma segunda instância do Visual Studio. Abra o projeto de console que você usou antes. Agora você deve ver a lâmpada aparecer no local onde está sua nova expressão de criação de objeto para ImmutableArray<int>. Se você pressionar Ctrl+. (ponto), verá sua correção de código e verá uma visualização de diferenças de código gerada automaticamente na interface do usuário da lâmpada. Roslyn cria isso para você.

Dica de Especialista: Se você iniciar a segunda instância do Visual Studio e não vir a lâmpada com a correção de código, talvez seja necessário limpar o cache de componentes do Visual Studio. Limpar o cache força o Visual Studio a reexaminar os componentes, assim, o Visual Studio deve carregar automaticamente seu componente mais recente. Primeiro, desligue a segunda instância do Visual Studio. Em seguida, em do Windows Explorer, navegue até %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. (O "16.0" muda de versão para versão com o Visual Studio.) Excluir o subdiretório ComponentModelCache.

Falar em vídeo e concluir o projeto de código

Você pode ver todo o código concluído aqui. Cada uma das subpastas DoNotUseImmutableArrayCollectionInitializer e DoNotUseImmutableArrayCtor tem um arquivo C# para encontrar problemas e um arquivo C# que implementa as correções de código que aparecem na interface do usuário da lâmpada do Visual Studio. Observe que o código finalizado possui mais abstração para evitar buscar repetidamente o objeto de tipo ImmutableArray<T>. Ele usa ações registradas aninhadas para salvar o objeto tipo em um contexto que está disponível sempre que as sub-ações (analisar a criação de objetos e analisar as inicializações de coleções) são executadas.