Udostępnij za pośrednictwem


Analizatory Roslyn i biblioteka z obsługą kodu dla funkcji ImmutableArrays

Platforma kompilatora .NET ("Roslyn") ułatwia tworzenie bibliotek obsługujących kod. Biblioteka z obsługą kodu udostępnia funkcje, których można używać i narzędzi (analizatory Roslyn), aby ułatwić korzystanie z biblioteki w najlepszy sposób lub unikanie błędów. W tym temacie przedstawiono sposób tworzenia rzeczywistego analizatora Roslyn w celu przechwytywania typowych błędów podczas korzystania z pakietu NuGet System.Collections.Immutable . W przykładzie pokazano również, jak podać poprawkę kodu dla problemu z kodem wykrytego przez analizator. Użytkownicy widzą poprawki kodu w interfejsie użytkownika żarówki programu Visual Studio i mogą automatycznie zastosować poprawkę dla kodu.

Rozpocznij

Do utworzenia tego przykładu potrzebne są następujące elementy:

  • Program Visual Studio 2015 (nie wersja Express) lub nowsza wersja. Możesz użyć bezpłatnej wersji Visual Studio Community Edition
  • Visual Studio SDK. Podczas instalowania programu Visual Studio możesz również sprawdzić narzędzia rozszerzalności programu Visual Studio w obszarze Common Tools , aby zainstalować zestaw SDK w tym samym czasie. Jeśli masz już zainstalowany program Visual Studio, możesz również zainstalować ten zestaw SDK, przechodząc do menu głównego Plik>nowy>projekt, wybierając pozycję C# w okienku nawigacji po lewej stronie, a następnie wybierając pozycję Rozszerzalność. Po wybraniu szablonu projektu "Zainstaluj narzędzia rozszerzeń programu Visual Studio" zostanie wyświetlony monit o pobranie i zainstalowanie zestawu SDK.
  • Zestaw SDK platformy kompilatora .NET ("Roslyn"). Możesz również zainstalować ten zestaw SDK, przechodząc do menu głównego Plik>nowy>projekt, wybierając pozycję C# w okienku nawigacji po lewej stronie, a następnie wybierając pozycję Rozszerzalność. Po wybraniu szablonu projektu "Pobierz zestaw SDK platformy kompilatora .NET" zostanie wyświetlony monit o pobranie i zainstalowanie zestawu SDK. Ten zestaw SDK zawiera wizualizator składni Roslyn. To przydatne narzędzie ułatwia ustalenie, jakie typy modeli kodu należy szukać w analizatorze. Infrastruktura analizatora wywołuje kod dla określonych typów modeli kodu, więc kod jest wykonywany tylko w razie potrzeby i może skupić się tylko na analizowaniu odpowiedniego kodu.

Jaki jest problem?

Wyobraź sobie, że udostępniasz bibliotekę z obsługą funkcji ImmutableArray (na przykład System.Collections.Immutable.ImmutableArray<T>). Deweloperzy języka C# mają wiele doświadczenia w pracy z tablicami .NET. Jednak ze względu na charakter technik ImmutableArrays i optymalizacji używanych w implementacji intuicje deweloperów języka C# powodują, że użytkownicy biblioteki mogą pisać uszkodzony kod, jak wyjaśniono poniżej. Ponadto użytkownicy nie widzą swoich błędów do czasu wykonywania, co nie jest środowiskiem jakości używanym w programie Visual Studio z platformą .NET.

Użytkownicy znają pisanie kodu w następujący sposób:

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

Tworzenie pustych tablic w celu wypełnienia kolejnych wierszy kodu i używanie składni inicjatora kolekcji jest znane deweloperom języka C#. Jednak napisanie tego samego kodu dla niezmiennejarray ulega awarii w czasie wykonywania:

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

Pierwszy błąd jest spowodowany użyciem struktury immutableArray w celu opakowania bazowego magazynu danych. Struktury muszą mieć konstruktory bez parametrów, aby default(T) wyrażenia mogły zwracać struktury ze wszystkimi elementami członkowskimi zerowymi lub null. Gdy kod uzyskuje dostęp do b1.Lengthmetody , występuje błąd wyłuszczaniu wartości null w czasie wykonywania, ponieważ w strukturę ImmutableArray nie ma podstawowej macierzy magazynowej. Prawidłowym sposobem utworzenia pustej niezmiennejarray jest ImmutableArray<int>.Empty.

Błąd z inicjatorami kolekcji występuje, ponieważ ImmutableArray.Add metoda zwraca nowe wystąpienia za każdym razem, gdy go wywołujesz. Ponieważ funkcja ImmutableArrays nigdy się nie zmienia, podczas dodawania nowego elementu zostanie przywrócony nowy obiekt ImmutableArray (który może współużytkować magazyn ze względów wydajności z wcześniej istniejącym niezmiennymArrayem). Ponieważ b2 wskazuje pierwszy niezmiennyArray przed wywołaniem Add() pięć razy, b2 jest to domyślna niezmiennaarray. Wywołanie metody Length na nim powoduje również awarię z powodu błędu wyłudzenia wartości null. Prawidłowym sposobem inicjowania niezmiennejarray bez ręcznego wywoływania metody Add jest użycie metody ImmutableArray.CreateRange(new int[] {1, 2, 3, 4, 5}).

Znajdowanie odpowiednich typów węzłów składni w celu wyzwolenia analizatora

Aby rozpocząć tworzenie analizatora, najpierw dowiedz się, jakiego typu składnię należy wyszukać. Uruchom program Syntax Visualizer z menu Wyświetl>inne wizualizator składni systemu Windows>Roslyn.

Umieść daszek edytora w wierszu, który deklaruje b1element . Zobaczysz, że program Syntax Visualizer znajduje się w węźle LocalDeclarationStatement drzewa składni. Ten węzeł ma element VariableDeclaration, który z kolei ma element , który z kolei ma VariableDeclaratorEqualsValueClauseelement , a na koniec istnieje element ObjectCreationExpression. Po kliknięciu drzewa składni wizualizatora węzłów składnia w oknie edytora wyróżnia się, aby wyświetlić kod reprezentowany przez ten węzeł. Nazwy podtypów SyntaxNode są zgodne z nazwami używanymi w gramatyce języka C#.

Tworzenie projektu analizatora

W menu głównym wybierz pozycję Plik>nowy>projekt. W oknie dialogowym Nowy projekt w obszarze Projekty języka C# na lewym pasku nawigacyjnym wybierz pozycję Rozszerzalność, a w okienku po prawej stronie wybierz szablon projektu Analizator z poprawką kodu. Wprowadź nazwę i potwierdź okno dialogowe.

Szablon otwiera plik DiagnosticAnalyzer.cs . Wybierz kartę buforu edytora. Ten plik ma klasę analizatora (utworzoną na podstawie nazwy nadanej projektowi), która pochodzi z DiagnosticAnalyzer (typ interfejsu API Roslyn). Nowa klasa ma deklarowanie DiagnosticAnalyzerAttribute analizatora jest istotne dla języka C#, dzięki czemu kompilator odnajduje i ładuje analizatora.

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

Analizator można zaimplementować przy użyciu języka Visual Basic, który jest przeznaczony dla kodu języka C# i odwrotnie. W elemecie DiagnosticAnalyzerAttribute ważniejsze jest wybranie, czy analizator jest przeznaczony dla jednego języka, czy obu tych elementów. Bardziej zaawansowane analizatory, które wymagają szczegółowego modelowania języka, mogą dotyczyć tylko jednego języka. Jeśli na przykład analizator sprawdza tylko nazwy typów lub publiczne nazwy elementów członkowskich, może być możliwe użycie modelu języka wspólnego oferty Roslyn w języku Visual Basic i C#. Na przykład FxCop ostrzega, że klasa implementuje ISerializableelement , ale klasa nie ma atrybutu SerializableAttribute niezależnego od języka i działa zarówno dla kodu Visual Basic, jak i C#.

Inicjowanie analizatora

Przewiń w dół trochę w DiagnosticAnalyzer klasie, aby zobaczyć metodę Initialize . Kompilator wywołuje tę metodę podczas aktywowania analizatora. Metoda przyjmuje AnalysisContext obiekt, który umożliwia analizatorowi uzyskanie informacji kontekstowych i zarejestrowanie wywołań zwrotnych dla zdarzeń dla rodzaju kodu, który chcesz przeanalizować.

public override void Initialize(AnalysisContext context) {}

Otwórz nowy wiersz w tej metodzie i wpisz "context", aby wyświetlić listę uzupełniania funkcji IntelliSense. Na liście uzupełniania znajduje się wiele Register... metod obsługi różnych rodzajów zdarzeń. Na przykład pierwszy RegisterCodeBlockAction, , wywołuje z powrotem kod dla bloku, który jest zwykle kodem między nawiasami klamrowymi. Rejestrowanie w bloku wywołuje również kod inicjatora pola, wartość nadaną atrybutowi lub wartość opcjonalnego parametru.

W innym przykładzie RegisterCompilationStartActionfunkcja wywołuje kod na początku kompilacji, co jest przydatne, gdy trzeba zebrać stan w wielu lokalizacjach. Możesz utworzyć strukturę danych, powiedzmy, aby zebrać wszystkie używane symbole, a za każdym razem, gdy analizator jest wywoływany z powrotem dla jakiejś składni lub symbolu, możesz zapisać informacje o każdej lokalizacji w strukturze danych. Po wywołaniu z powrotem z powodu zakończenia kompilacji możesz przeanalizować wszystkie zapisane lokalizacje, na przykład, aby zgłosić, jakich symboli używa kod z każdej using instrukcji.

Korzystając z wizualizatora składni, wiesz, że chcesz być wywoływany, gdy kompilator przetwarza obiektCreationExpression. Ten kod służy do konfigurowania wywołania zwrotnego:

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

Rejestrujesz się w węźle składniowym i filtrujesz tylko węzły składni tworzenia obiektów. Zgodnie z konwencją autorzy analizatorów używają lambda podczas rejestrowania akcji, co pomaga zachować analizatory bezstanowe. Aby utworzyć metodę, możesz użyć funkcji Visual Studio Generate From Usage (Generowanie na podstawie użycia).AnalyzeObjectCreation Spowoduje to wygenerowanie poprawnego typu parametru kontekstu.

Ustawianie właściwości dla użytkowników analizatora

Aby analizator był odpowiednio wyświetlany w interfejsie użytkownika programu Visual Studio, poszukaj i zmodyfikuj następujący wiersz kodu w celu zidentyfikowania analizatora:

internal const string Category = "Naming";

Zmień "Naming" na "API Guidance".

Następnie znajdź i otwórz plik Resources.resx w projekcie przy użyciu Eksplorator rozwiązań. Możesz umieścić w opisie analizatora, tytułu itp. Na razie możesz zmienić wartość dla wszystkich tych "Don't use ImmutableArray<T> constructor" elementów. Argumenty formatowania ciągów można umieścić w ciągu ({0}, {1}itp.), a później po wywołaniu Diagnostic.Create()metody można podać tablicę params argumentów, które mają zostać przekazane.

Analizowanie wyrażenia tworzenia obiektu

Metoda AnalyzeObjectCreation przyjmuje inny typ kontekstu dostarczonego przez strukturę analizatora kodu. Metoda Initialize AnalysisContext umożliwia rejestrowanie wywołań zwrotnych akcji w celu skonfigurowania analizatora. Element SyntaxNodeAnalysisContext, na przykład, ma wartość CancellationToken , którą można przekazać. Jeśli użytkownik zacznie wpisywać w edytorze, Narzędzie Roslyn anuluje uruchamianie analizatorów w celu zapisania pracy i zwiększenia wydajności. W innym przykładzie ten kontekst ma właściwość Node, która zwraca węzeł składni tworzenia obiektu.

Pobierz węzeł, który można założyć, jest typem, dla którego filtrowana jest akcja węzła składni:

var objectCreation = (ObjectCreationExpressionSyntax)context.Node;

Uruchamianie programu Visual Studio przy użyciu analizatora po raz pierwszy

Uruchom program Visual Studio, tworząc i wykonując analizator (naciśnij klawisz F5). Ponieważ projekt startowy w Eksplorator rozwiązań jest projektem VSIX, uruchomienie kodu kompiluje kod i VSIX, a następnie uruchamia program Visual Studio z zainstalowanym systemem VSIX. Po uruchomieniu programu Visual Studio w ten sposób uruchamia się z odrębnym gałąź rejestru, dzięki czemu główne użycie programu Visual Studio nie będzie miało wpływu na wystąpienia testowe podczas tworzenia analizatorów. Przy pierwszym uruchomieniu w ten sposób program Visual Studio wykonuje kilka inicjalizacji podobnych do po pierwszym uruchomieniu programu Visual Studio po jego zainstalowaniu.

Utwórz projekt konsoli, a następnie wprowadź kod tablicy do aplikacji konsolowych Main:

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

Wiersze kodu z ImmutableArray zakwasami, ponieważ musisz pobrać niezmienny pakiet NuGet i dodać instrukcję using do kodu. Naciśnij przycisk prawego wskaźnika w węźle projektu w Eksplorator rozwiązań i wybierz pozycję Zarządzaj pakietami NuGet. W menedżerze NuGet wpisz ciąg "Niezmienny" w polu wyszukiwania i wybierz element System.Collections.Immutable (nie wybieraj opcji Microsoft.Bcl.Immutable) w okienku po lewej stronie i naciśnij przycisk Zainstaluj w okienku po prawej stronie. Zainstalowanie pakietu powoduje dodanie odwołania do odwołań do projektu.

W obszarze ImmutableArraynadal widać czerwone zygzaki, więc umieść daszek w tym identyfikatorze i naciśnij klawisze Ctrl+. (kropka), aby wyświetlić sugerowane menu poprawki i wybrać dodanie odpowiedniej using instrukcji.

Zapisz wszystko i zamknij na razie drugie wystąpienie programu Visual Studio, aby przejść do stanu czystego.

Kończenie analizatora przy użyciu edycji i kontynuowania

W pierwszym wystąpieniu programu Visual Studio ustaw punkt przerwania na początku AnalyzeObjectCreation metody, naciskając klawisz F9 z daszkiem w pierwszym wierszu.

Uruchom ponownie analizator za pomocą klawisza F5, a w drugim wystąpieniu programu Visual Studio otwórz ponownie utworzoną ostatnio aplikację konsolową.

Powrócisz do pierwszego wystąpienia programu Visual Studio w punkcie przerwania, ponieważ kompilator Roslyn zobaczył wyrażenie tworzenia obiektu i został wywołany do analizatora.

Pobierz węzeł tworzenia obiektu. Przejdź przez wiersz, który ustawia zmienną objectCreation , naciskając klawisz F10, a w oknie bezpośrednim oblicz wyrażenie "objectCreation.ToString()". Zobaczysz, że węzeł składni, do którego wskazuje zmienna, to kod "new ImmutableArray<int>()", tylko to, czego szukasz.

Pobierz obiekt ImmutableArray<T> Type. Należy sprawdzić, czy tworzony typ to ImmutableArray. Najpierw uzyskasz obiekt reprezentujący ten typ. Typy są sprawdzane przy użyciu modelu semantycznego, aby upewnić się, że masz dokładnie właściwy typ i nie porównujesz ciągu z klasy ToString(). Wprowadź następujący wiersz kodu na końcu funkcji:

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

Typy ogólne są wyznaczane w metadanych z backticks (') i liczbą parametrów ogólnych. Dlatego nie widzisz "... ImmutableArray<T>" w nazwie metadanych.

Model semantyczny zawiera wiele przydatnych elementów, które umożliwiają zadawanie pytań dotyczących symboli, przepływu danych, zmiennego okresu istnienia itp. Roslyn oddziela węzły składni od modelu semantycznego z różnych powodów inżynieryjnych (wydajność, modelowanie błędnego kodu itp.). Chcesz, aby model kompilacji wyszukał informacje zawarte w odwołaniach w celu dokładnego porównania.

Żółty wskaźnik wykonywania można przeciągnąć po lewej stronie okna edytora. Przeciągnij go do wiersza, który ustawia zmienną objectCreation i przekroczy nowy wiersz kodu przy użyciu klawisza F10. Jeśli umieścisz wskaźnik myszy na zmiennej immutableArrayOfType, zobaczysz, że znaleźliśmy dokładny typ w modelu semantycznym.

Pobierz typ wyrażenia tworzenia obiektu. Wyrażenie "Type" jest używane na kilka sposobów w tym artykule, ale oznacza to, że jeśli masz wyrażenie "new Foo", musisz uzyskać model Foo. Musisz uzyskać typ wyrażenia tworzenia obiektu, aby sprawdzić, czy jest to typ T> ImmutableArray<. Użyj ponownie modelu semantycznego, aby uzyskać informacje o symbolu symbolu typu (ImmutableArray) w wyrażeniu tworzenia obiektu. Wprowadź następujący wiersz kodu na końcu funkcji:

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

Ponieważ analizator musi obsługiwać niekompletny lub niepoprawny kod w buforach edytora (na przykład brakuje using instrukcji), należy sprawdzić, czy symbolInfo jest to null. Aby zakończyć analizę, musisz pobrać nazwany typ (INamedTypeSymbol) z obiektu informacji o symbolu.

Porównaj typy. Ponieważ istnieje otwarty typ ogólny T, którego szukamy, a typ w kodzie jest konkretnym typem ogólnym, należy wykonać zapytanie o informacje o symbolach, z których jest skonstruowany typ (otwarty typ ogólny) i porównać ten wynik z immutableArrayOfTType. Wprowadź następujące elementy na końcu metody:

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

Zgłoś diagnostykę. Raportowanie diagnostyki jest dość proste. Regułę utworzoną dla Ciebie należy użyć w szablonie projektu, który jest zdefiniowany przed metodą Initialize. Ponieważ ta sytuacja w kodzie jest błędem, można zmienić wiersz, który zainicjował regułę, aby zastąpić DiagnosticSeverity.Warning (zielony wywiąż) ciągiem DiagnosticSeverity.Error (czerwony wywiąż). Pozostała część reguły inicjuje się z zasobów edytowanych na początku przewodnika. Należy również zgłosić lokalizację dla przełącznika, który jest lokalizacją specyfikacji typu wyrażenia tworzenia obiektu. Wprowadź ten kod w if bloku:

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

Funkcja powinna wyglądać następująco (być może sformatowana inaczej):

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

Usuń punkt przerwania, aby zobaczyć działanie analizatora (i zatrzymać powrót do pierwszego wystąpienia programu Visual Studio). Przeciągnij wskaźnik wykonywania na początek metody, a następnie naciśnij klawisz F5 , aby kontynuować wykonywanie. Po przełączeniu się z powrotem do drugiego wystąpienia programu Visual Studio kompilator zacznie ponownie badać kod i wywoła go do analizatora. W obszarze ImmutableType<int>można zobaczyć przełącznik .

Dodawanie poprawki kodu dla problemu z kodem

Przed rozpoczęciem zamknij drugie wystąpienie programu Visual Studio i zatrzymaj debugowanie w pierwszym wystąpieniu programu Visual Studio (w którym programujesz analizator).

Dodaj nową klasę. Użyj menu skrótów (przycisk prawego wskaźnika) w węźle projektu w Eksplorator rozwiązań i wybierz dodanie nowego elementu. Dodaj klasę o nazwie BuildCodeFixProvider. Ta klasa musi pochodzić z CodeFixProviderklasy i należy użyć klawiszy Ctrl+. (kropka) w celu wywołania poprawki kodu, która dodaje poprawną using instrukcję. Ta klasa musi być również oznaczona adnotacjami z atrybutem ExportCodeFixProvider i należy dodać instrukcję using , aby rozwiązać wyliczenie LanguageNames . Powinien być w nim plik klasy z następującym kodem:

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

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

Odcięcie elementów pochodnych. Teraz umieść daszek edytora w identyfikatorze CodeFixProvider i naciśnij klawisze Ctrl+. (kropka), aby wyprzeć implementację tej abstrakcyjnej klasy bazowej. Spowoduje to wygenerowanie właściwości i metody.

Zaimplementuj właściwość . FixableDiagnosticIds Wypełnij treść właściwości get następującym kodem:

return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);

Roslyn łączy diagnostykę i poprawki, pasując do tych identyfikatorów, które są tylko ciągami. Szablon projektu wygenerował identyfikator diagnostyczny i możesz go zmienić. Kod we właściwości zwraca tylko identyfikator z klasy analizatora.

Metoda RegisterCodeFixAsync przyjmuje kontekst. Kontekst jest ważny, ponieważ poprawka kodu może być stosowana do wielu diagnostyki lub może występować więcej niż jeden problem w wierszu kodu. W przypadku wpisania ciągu "context" w treści metody lista uzupełniania funkcji IntelliSense wyświetli kilka przydatnych elementów członkowskich. Istnieje element członkowski CancellationToken, który można sprawdzić, czy coś chce anulować poprawkę. Istnieje element członkowski dokumentu, który zawiera wiele przydatnych elementów członkowskich i umożliwia dostęp do obiektów modelu projektu i rozwiązania. Istnieje element członkowski Span, który jest początkiem i końcem lokalizacji kodu określonej podczas raportowania diagnostyki.

Ustaw metodę jako asynchronizuj. Pierwszą rzeczą, którą należy wykonać, jest naprawienie wygenerowanej deklaracji metody jako async metody. Poprawka kodu umożliwiająca wycięcie implementacji klasy abstrakcyjnej nie zawiera async słowa kluczowego, mimo że metoda zwraca Taskwartość .

Pobierz katalog główny drzewa składni. Aby zmodyfikować kod, należy utworzyć nowe drzewo składni ze zmianami wprowadzanymi przez poprawkę kodu. Potrzebny jest Document element z kontekstu, aby wywołać metodę GetSyntaxRootAsync. Jest to metoda asynchronicznie, ponieważ istnieje nieznana praca w celu pobrania drzewa składni, ewentualnie pobrania pliku z dysku, przeanalizowanie go i utworzenie dla niego modelu kodu Roslyn. W tym czasie interfejs użytkownika programu Visual Studio powinien odpowiadać, co jest używane.async Zastąp wiersz kodu w metodzie następującym kodem:

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

Znajdź węzeł z problemem. Przekazujesz zakres kontekstu, ale odnaleziony węzeł może nie być kodem, który trzeba zmienić. Zgłoszona diagnostyka dostarczyła tylko zakres identyfikatora typu (gdzie należy wywiórka), ale musisz zastąpić całe wyrażenie tworzenia obiektu, w tym new słowo kluczowe na początku i nawiasy na końcu. Dodaj następujący kod do metody (i użyj klawiszy Ctrl+, aby dodać instrukcję using dla ObjectCreationExpressionSyntaxelementu ):

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

Zarejestruj poprawkę kodu dla interfejsu użytkownika żarówki. Po zarejestrowaniu poprawki kodu aplikacja Roslyn automatycznie podłącza się do interfejsu użytkownika żarówki programu Visual Studio. Użytkownicy końcowi zobaczą, że mogą używać klawiszy Ctrl+. (kropka), gdy analizator zwija nieprawidłowe ImmutableArray<T> użycie konstruktora. Ponieważ dostawca poprawki kodu wykonuje tylko wtedy, gdy występuje problem, możesz założyć, że masz szukane wyrażenie tworzenia obiektu. Z parametru kontekstu można zarejestrować nową poprawkę kodu, dodając następujący kod na końcu RegisterCodeFixAsync metody:

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

Musisz umieścić daszek edytora w identyfikatorze , CodeActiona następnie użyć klawisza Ctrl+ (kropka), aby dodać odpowiednią using instrukcję dla tego typu.

Następnie umieść daszek edytora w identyfikatorze ChangeToImmutableArrayEmpty i ponownie użyj klawisza Ctrl+, aby wygenerować ten wycinkę metody.

Ten ostatni fragment kodu, który został dodany, rejestruje poprawkę kodu, przekazując element CodeAction i identyfikator diagnostyczny dla rodzaju znalezionego problemu. W tym przykładzie istnieje tylko jeden identyfikator diagnostyczny, dla którego ten kod zawiera poprawki, więc wystarczy przekazać pierwszy element tablicy identyfikatorów diagnostycznych. Podczas tworzenia elementu CodeActionnależy przekazać tekst, którego interfejs użytkownika żarówki powinien używać jako opisu poprawki kodu. Przekazujesz również funkcję, która przyjmuje element CancellationToken i zwraca nowy dokument. Nowy dokument zawiera nowe drzewo składni, które zawiera poprawiony kod, który wywołuje element ImmutableArray.Empty. Ten fragment kodu używa wyrażenia lambda, aby można było go zamknąć za pośrednictwem węzła objectCreation i dokumentu kontekstu.

Skonstruuj nowe drzewo składni. W metodzie ChangeToImmutableArrayEmpty , której wycinków wygenerowano wcześniej, wprowadź wiersz kodu: ImmutableArray<int>.Empty;. Jeśli ponownie wyświetlisz okno narzędzia Składnia Wizualizator , zobaczysz, że ta składnia jest węzłem SimpleMemberAccessExpression. To właśnie ta metoda musi konstruować i zwracać w nowym dokumencie.

Pierwszą zmianą ChangeToImmutableArrayEmpty metody jest dodanie async wcześniej Task<Document> , ponieważ generatory kodu nie mogą zakładać, że metoda powinna być asynchronizuj.

Wypełnij treść następującym kodem, aby metoda wyglądała podobnie do następującej:

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

Musisz umieścić daszek edytora w identyfikatorze SyntaxGenerator i użyć klawisza Ctrl+( kropka), aby dodać odpowiednią using instrukcję dla tego typu.

Ten kod używa elementu SyntaxGenerator, który jest przydatnym typem do konstruowania nowego kodu. Po uzyskaniu generatora dokumentu, który ma problem z kodem, ChangeToImmutableArrayEmpty wywołuje MemberAccessExpressionmetodę , przekazując typ, do którego chcemy uzyskać dostęp, i przekazując nazwę elementu członkowskiego jako ciąg.

Następnie metoda pobiera katalog główny dokumentu i dlatego, że może to obejmować dowolną pracę w ogólnym przypadku, kod czeka na to wywołanie i przekazuje token anulowania. Modele kodu Roslyn są niezmienne, takie jak praca z ciągiem platformy .NET; po zaktualizowaniu ciągu zostanie zwrócony nowy obiekt ciągu. Po wywołaniu ReplaceNodemetody zostanie przywrócony nowy węzeł główny. Większość drzewa składni jest współużytkowana (ponieważ jest niezmienna), ale objectCreation węzeł jest zastępowany węzłem memberAccess , a także wszystkie węzły nadrzędne do katalogu głównego drzewa składni.

Wypróbuj poprawkę kodu

Teraz możesz nacisnąć klawisz F5 , aby wykonać analizator w drugim wystąpieniu programu Visual Studio. Otwórz wcześniej użyty projekt konsoli. Teraz powinna zostać wyświetlona żarówka, w której znajduje się nowe wyrażenie tworzenia obiektu dla elementu ImmutableArray<int>. Jeśli naciśniesz klawisze Ctrl+. (kropka), zobaczysz poprawkę kodu i zobaczysz automatycznie wygenerowany podgląd różnicy kodu w interfejsie użytkownika żarówki. Roslyn tworzy to dla Ciebie.

Porada pro: jeśli uruchomisz drugie wystąpienie programu Visual Studio i nie widzisz żarówki z poprawką kodu, może być konieczne wyczyszczenie pamięci podręcznej składników programu Visual Studio. Wyczyszczenie pamięci podręcznej wymusza ponowne sprawdzenie składników programu Visual Studio, dlatego program Visual Studio powinien następnie pobrać najnowszy składnik. Najpierw zamknij drugie wystąpienie programu Visual Studio. Następnie w Eksploratorze Windows przejdź do folderu %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. (Wersja "16.0" zmienia się z wersji na wersję za pomocą programu Visual Studio). Usuń podkatalog ComponentModelCache.

Porozmawiaj z wideo i zakończ projekt kodu

W tym miejscu możesz zobaczyć cały gotowy kod. Podfoldery DoNotUseImmutableArrayCollectionInitializer i DoNotUseImmutableArrayCtor mają plik C# do znajdowania problemów i pliku C#, który implementuje poprawki kodu wyświetlane w interfejsie użytkownika żarówki programu Visual Studio. Należy pamiętać, że gotowy kod ma nieco więcej abstrakcji, aby uniknąć pobierania obiektu typu ImmutableArray<T> w porównaniu z obiektem i przez nie. Używa zagnieżdżonych zarejestrowanych akcji, aby zapisać obiekt typu w kontekście dostępnym za każdym razem, gdy podrzędne akcje (analizowanie tworzenia obiektów i analizowania inicjalizacji kolekcji) są wykonywane.