Analizatory Roslyn i biblioteka świadoma kodu dla ImmutableArrays
.NET Compiler Platform ("Roslyn") ułatwia tworzenie bibliotek obsługujących kod. Biblioteka świadoma kodu udostępnia funkcjonalność i narzędzia (analizatory Roslyn), które pomagają jak najlepiej korzystać z biblioteki lub unikać błędów. W tym temacie przedstawiono sposób tworzenia rzeczywistego analizatora Roslyn w celu przechwytywania typowych błędów podczas korzystania z pakietu System.Collections.Immutable NuGet. 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.
Zacznij
Do utworzenia tego przykładu potrzebne są następujące elementy:
- Program Visual Studio 2015 (nie wersja Express) lub nowsza wersja. Można używać bezpłatnej wersji Visual Studio Community Edition
- zestaw SDK programu Visual Studio. Podczas instalacji programu Visual Studio możesz również wybrać opcję Narzędzia rozszerzalności programu Visual Studio w sekcji Common Tools, aby jednocześnie zainstalować zestaw SDK. 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 "Install the Visual Studio Extensibility Tools" zostanie wyświetlony monit o pobranie i zainstalowanie zestawu SDK.
- .NET Compiler Platform („Roslyn”) SDK. Ten SDK można również zainstalować, przechodząc do menu głównego Plik>Nowy>Projekt, wybierając C# w okienku nawigacji po lewej stronie, a następnie wybierając Rozszerzalność. Po wybraniu opcji "Pobierz zestaw SDK platformy kompilatora .NET" w szablonie projektu typu breadcrumb, zostanie wyświetlony monit o pobranie i zainstalowanie zestawu SDK. Ten 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 C# mają duże doświadczenie 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 zauważają swoich błędów aż do momentu uruchomienia, co nie jest jakością doświadczenia, do której są przyzwyczajeni 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 ImmutableArray powoduje awarię 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 przez implementację ImmutableArray, która używa struktury do 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.Length
, występuje błąd odniesienia do wartości null w czasie wykonywania, ponieważ w strukturze ImmutableArray nie ma podstawowej tablicy przechowującej. Prawidłowym sposobem utworzenia pustego ImmutableArray jest ImmutableArray<int>.Empty
.
Błąd podczas inicjowania kolekcji występuje, ponieważ metoda ImmutableArray.Add
zwraca nowe wystąpienia za każdym razem, gdy go wywołujesz. Ponieważ ImmutableArrays nigdy się nie zmieniają, podczas dodawania nowego elementu otrzymasz nowy obiekt ImmutableArray (który może współużytkować pamięć ze względów wydajności z wcześniej istniejącym ImmutableArray). Ponieważ b2
wskazuje na pierwszą niezmienną tablicę przed pięciokrotnym wywołaniem Add()
, b2
jest domyślną niezmienną tablicą. Wywołanie właściwości Length na nim również powoduje awarię z powodu błędu odwołania do wartości null. Prawidłowym sposobem inicjowania ImmutableArray bez ręcznego wywoływania metody Add jest użycie ImmutableArray.CreateRange(new int[] {1, 2, 3, 4, 5})
.
Znajdź odpowiednie typy węzłów składniowych, aby wyzwolić analizator
Aby rozpocząć tworzenie analizatora, najpierw zidentyfikuj, jakiego typu SyntaxNode należy szukać. Uruchom Syntax Visualizer z menu View>Other Windows>Roslyn Syntax Visualizer.
Umieść kursor edytora w wierszu, który deklaruje b1
. Zobaczysz, że program Syntax Visualizer pokazuje, że znajdujesz się w węźle LocalDeclarationStatement
drzewa składniowego. Ten węzeł ma VariableDeclaration
, który z kolei ma VariableDeclarator
, który z kolei ma EqualsValueClause
, a na koniec istnieje ObjectCreationExpression
. W przypadku kliknięcia w drzewo węzłów wizualizatora składni, składnia w oknie edytora zostaje podświetlona, aby pokazać kod reprezentowany przez ten węzeł. Nazwy podtypów SyntaxNode są zgodne z nazwami używanymi w gramatyce języka C#.
Tworzenie projektu analizatora
Z menu głównego wybierz pozycję Plik >Nowy>Projekt. W oknie dialogowym Nowy Projekt, w obszarze C# projektów na pasku nawigacyjnym po lewej stronie, wybierz 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
(typu interfejsu API Roslyn). Nowa klasa zawiera deklarację DiagnosticAnalyzerAttribute
, wskazującą, że analizator jest powiązany z językiem C#, co umożliwia kompilatorowi odnalezienie i załadowanie tego 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 atrybucie DiagnosticAnalyzerAttribute ważniejsze jest wybranie, czy analizator jest przeznaczony dla jednego języka, czy obu. Bardziej zaawansowane analizatory, które wymagają szczegółowego modelowania języka, mogą skoncentrować się tylko na jednym języku. Jeśli na przykład analizator sprawdza tylko nazwy typów lub nazwy członków publicznych, może być możliwe użycie wspólnego modelu językowego, który oferuje Roslyn, dla języków Visual Basic i C#. Na przykład FxCop ostrzega, że klasa implementuje ISerializable, ale klasa nie ma atrybutu SerializableAttribute jest niezależna od języka i działa zarówno dla kodu Visual Basic, jak i C#.
Inicjowanie analizatora
Przewiń w dół nieco w klasie DiagnosticAnalyzer
, aby wyświetlić metodę Initialize
. Kompilator wywołuje tę metodę podczas aktywowania analizatora. Metoda przyjmuje obiekt AnalysisContext
, który umożliwia analizatorowi uzyskiwanie informacji kontekstowych i rejestrowanie 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 z nich, RegisterCodeBlockAction
, wywołuje twój kod dla bloku, który zazwyczaj znajduje się między nawiasami klamrowymi. Rejestrowanie do bloku także powoduje wywołanie zwrotne twojego kodu dla inicjatora pola, nadanej wartości atrybutu lub wartości opcjonalnego parametru.
Na innym przykładzie, RegisterCompilationStartAction
odwołuje się do Twojego kodu na początku kompilacji, co jest przydatne, gdy trzeba zebrać stan w wielu miejscach. 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. Gdy zostaniesz ponownie wywołany po zakończeniu kompilacji, możesz przeanalizować wszystkie zapisane lokalizacje, na przykład, aby zgłosić, z jakich symboli korzysta kod w każdej instrukcji using
.
Korzystając z Syntax Visualizer, dowiesz się, że chcesz zostać wywołany, gdy kompilator przetwarza wyrażenie tworzenia obiektu (ObjectCreationExpression). 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ą wyrażenia lambda podczas rejestrowania akcji, co pomaga zachować bezstanowość analizatorów. Możesz użyć funkcji Visual Studio Generowanie na podstawie użycia, aby utworzyć metodę AnalyzeObjectCreation
. Spowoduje to wygenerowanie poprawnego typu parametru kontekstu również dla ciebie.
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 eksploratora rozwiązań . Możesz dodać opis dla swojego analizatora, tytuł itp. Na razie możesz zmienić wartość wszystkich tych elementów na "Don't use ImmutableArray<T> constructor"
. Argumenty formatowania ciągów można umieścić w ciągu ({0}, {1}itp.), a później podczas wywoływania Diagnostic.Create()
można podać tablicę params
argumentów do przekazania.
Analizowanie wyrażenia tworzenia obiektu
Metoda AnalyzeObjectCreation
przyjmuje inny typ kontekstu dostarczonego przez strukturę analizatora kodu.
AnalysisContext
metody Initialize
umożliwia rejestrowanie wywołań zwrotnych akcji w celu skonfigurowania analizatora. Na przykład SyntaxNodeAnalysisContext
ma CancellationToken
, które można obejść. Jeśli użytkownik zacznie wpisywać w edytorze, Roslyn anuluje uruchamianie analizatorów w celu oszczędzania pracy i zwiększenia wydajności systemu. Jako kolejny przykład, ten kontekst ma właściwość o nazwie Node, która zwraca węzeł składni tworzenia obiektu.
Pobierz węzeł, który możesz założyć, że jest typem, dla którego przefiltrowano akcję 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 F5). Ponieważ projekt startowy w Eksploratorze rozwiązań to projekt VSIX, uruchomienie kodu powoduje skompilowanie tego kodu i pakietu VSIX, a następnie uruchamia program Visual Studio z zainstalowanym tym pakietem VSIX. Po uruchomieniu programu Visual Studio w ten sposób, uruchamia się on z odrębną gałęzią rejestru, dzięki czemu główne wykorzystanie programu Visual Studio nie będzie miało wpływu na instancje 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 konsolowy, a następnie wprowadź kod tablicy do metody Main w aplikacji konsolowej.
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 oznaczeniem ImmutableArray
mają podkreślenia, ponieważ musisz pobrać pakiet NuGet zawierający niezmienne elementy i dodać instrukcję using
do swojego kodu. Naciśnij prawy przycisk wskaźnika w węźle projektu w eksploratorze rozwiązań i wybierz pozycję Zarządzaj pakietami NuGet. W menedżerze NuGet wpisz "Immutable" w polu wyszukiwania i wybierz element System.Collections.Immutable (nie wybieraj Microsoft.Bcl.Immutable) w panelu po lewej stronie i naciśnij przycisk Zainstaluj w panelu po prawej stronie. Zainstalowanie pakietu dodaje odwołanie do odniesień projektu.
W obszarze ImmutableArray
są nadal widoczne czerwone zygzaki, dlatego umieść kursor w tym identyfikatorze i naciśnij Ctrl+oraz (kropka), aby wyświetlić sugerowane menu poprawki i wybrać dodanie odpowiedniego polecenia using
.
Zapisz wszystko i zamknij drugie wystąpienie programu Visual Studio, aby na razie przejść do stanu czystego.
Skończ analizator, używając funkcji edycji i kontynuacji
W pierwszym wystąpieniu programu Visual Studio ustaw punkt przerwania na początku metody AnalyzeObjectCreation
, naciskając F9, gdy kursor znajduje się przy pierwszej linii.
Uruchom ponownie analizator za pomocą 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 napotkał wyrażenie tworzenia obiektu i wywołał Twój analizator.
Pobierz węzeł tworzenia obiektu. Przejdź przez wiersz, który ustawia zmienną objectCreation
, naciskając F10, a w oknie natychmiastowego oblicz wyrażenie "objectCreation.ToString()"
. Zobaczysz, że węzeł składni, na który wskazuje zmienna, to kod "new ImmutableArray<int>()"
, dokładnie 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 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 wyznaczasz w metadanych za pomocą backticks (`) i liczby 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.
Można przeciągnąć żółty wskaźnik wykonywania znajdujący się po lewej stronie okna edytora. Przeciągnij go w górę do wiersza, który ustawia zmienną objectCreation
, i przejdź do nowego wiersza kodu przy użyciu klawiszy 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 ImmutableArray<T>. Użyj ponownie modelu semantycznego, aby uzyskać informacje o 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 instrukcji using
), należy sprawdzić, czy symbolInfo
jest równe 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, zapytujesz o informacje o symbolach dla typu skonstruowanego z otwartego typu ogólnego i porównujesz 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. Używasz reguły stworzonej specjalnie dla Ciebie w szablonie projektu, która jest zdefiniowana przed metodą Initialize. Ponieważ ta sytuacja w kodzie jest błędem, można zmienić wiersz inicjujący Regułę, aby zastąpić DiagnosticSeverity.Warning
(zielona falista linia) z DiagnosticSeverity.Error
(czerwona falista linia). Pozostała część reguły jest inicjalizowana na podstawie zasobów, które edytowałeś na początku przewodnika. Należy również zgłosić lokalizację dla podkreślenia, która jest lokalizacją specyfikacji typu wyrażenia tworzenia obiektu. Wprowadź ten kod w bloku if
:
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 móc zobaczyć działanie analizatora (i zapobiec powracaniu do pierwszego uruchomienia Visual Studio). Przeciągnij wskaźnik wykonywania na początek metody, a następnie naciśnij 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. Widać falistą linię pod ImmutableType<int>
.
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 eksploratorze rozwiązań i wybierz dodanie nowego elementu. Dodaj klasę o nazwie BuildCodeFixProvider
. Ta klasa musi pochodzić z CodeFixProvider
i należy użyć Ctrl+. (kropka) w celu wywołania poprawki kodu, która dodaje poprawną instrukcję using
. Ta klasa musi być również oznaczona adnotacjami za pomocą atrybutu 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ść kursor edytora w identyfikatorze CodeFixProvider
i naciśnij Ctrl+. (kropka), aby utworzyć szkic implementacji dla tej abstrakcyjnej klasy bazowej. Spowoduje to wygenerowanie właściwości i metody.
Zaimplementuj właściwość . Wypełnij treść właściwości FixableDiagnosticIds
ciała get
następującym kodem:
return ImmutableArray.Create(ImmutableArrayAnalyzer.DiagnosticId);
Roslyn łączy diagnostykę i poprawki, dopasowując te identyfikatory, które są po prostu ciągami znaków. Szablon projektu wygenerował identyfikator diagnostyczny i możesz go zmienić. Kod w ramach 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 diagnostyk, lub na jednej linii kodu może występować więcej niż jeden problem. Jeśli wpiszesz "context." w treści metody, lista uzupełniania IntelliSense wyświetli kilka przydatnych elementów. Istnieje element członkowski CancellationToken, który można sprawdzić, aby zobaczyć, czy zachodzi potrzeba anulowania poprawki. 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 Span, który wyznacza początek i koniec lokalizacji kodu określonej w momencie raportowania diagnostyki.
Zmień metodę na asynchroniczną. Pierwszą rzeczą, którą należy zrobić, jest poprawienie wygenerowanej deklaracji metody, aby była metodą async
. Poprawka kodu umożliwiająca wycięcie implementacji klasy abstrakcyjnej nie zawiera słowa kluczowego async
, mimo że metoda zwraca Task
.
Pobierz korzeń drzewa składniowego. Aby zmodyfikować kod, należy utworzyć nowe drzewo składni ze zmianami wprowadzanymi przez poprawkę kodu. Potrzebujesz Document
z kontekstu, aby wywołać GetSyntaxRootAsync
. Jest to metoda asynchroniczna, ponieważ nieznane są działania potrzebne do otrzymania drzewa składniowego, ewentualnie pobrania pliku z dysku, jego przeanalizowania i utworzenia modelu kodu Roslyn. Podczas tego działania interfejs użytkownika Visual Studio powinien być responsywny, co umożliwia 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 węzeł, który znajdziesz, może nie być tym kodem, który trzeba zmienić. Zgłoszona diagnostyka dostarczyła tylko zakres dla identyfikatora typu (gdzie znajdował się znaczek), ale musisz zastąpić całe wyrażenie tworzenia obiektu, w tym słowo kluczowe new
na początku i nawiasy na końcu. Dodaj następujący kod do metody (i użyj Ctrl+., aby dodać instrukcję using
dla ObjectCreationExpressionSyntax
):
var objectCreation = root.FindNode(context.Span)
.FirstAncestorOrSelf<ObjectCreationExpressionSyntax>();
Zarejestruj poprawkę kodu dla interfejsu użytkownika żarówki. Kiedy zarejestrujesz poprawkę kodu, Roslyn automatycznie integruje się z interfejsem użytkownika żarówki (light bulb UI) programu Visual Studio. Użytkownicy końcowi zobaczą, że mogą używać Ctrl+oraz (kropka), gdy analizator podkreśla falistą linią nieprawidłowe użycie konstruktora ImmutableArray<T>
. 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 metody RegisterCodeFixAsync
:
context.RegisterCodeFix(
CodeAction.Create("Use ImmutableArray<T>.Empty",
c => ChangeToImmutableArrayEmpty(objectCreation,
context.Document,
c)),
context.Diagnostics[0]);
Musisz umieścić kursor edytora w identyfikatorze, CodeAction
, i następnie używasz Ctrl+. (kropka), aby dodać odpowiednią dla tego typu instrukcję using
.
Następnie umieść kursor edytora w identyfikatorze ChangeToImmutableArrayEmpty
i użyj Ctrl+. ponownie, aby wygenerować dla Ciebie szkielet tej metody.
Ten ostatni fragment kodu, który został dodany, rejestruje poprawkę kodu, przekazując 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 CodeAction
należy przekazać tekst, którego interfejs użytkownika żarówki powinien używać jako opisu poprawki kodu. Przekazujesz również funkcję, która przyjmuje CancellationToken i zwraca nowy Document. Nowy dokument posiada nowe drzewo składni, które zawiera twój zmodyfikowany kod wywołujący 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 szkic funkcji wygenerowano wcześniej, wprowadź linijkę kodu: ImmutableArray<int>.Empty;
. Jeśli ponownie wyświetlisz okno narzędzia wizualizatora składni , możesz zobaczyć, że ta składnia jest węzłem SimpleMemberAccessExpression. Metoda musi konstruować i zwracać to jako nowy dokument.
Pierwszą zmianą ChangeToImmutableArrayEmpty
jest dodanie async
przed Task<Document>
, ponieważ generatory kodu nie mogą zakładać, że metoda powinna być asynchroniczna.
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ć kursor edytora w identyfikatorze SyntaxGenerator
i użyć Ctrl+. (kropka), aby dodać odpowiednią instrukcję using
dla tego typu.
Ten kod używa SyntaxGenerator
, który jest przydatnym typem do konstruowania nowego kodu. Po uzyskaniu generatora dla dokumentu, który ma problem z kodem, ChangeToImmutableArrayEmpty
wywołuje MemberAccessExpression
, przekazując typ, który zawiera element, do którego chcemy uzyskać dostęp, oraz przekazując nazwę tego elementu 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, tak jak praca z ciągiem tekstowym w .NET; po zaktualizowaniu ciągu tekstowego, otrzymujesz nowy obiekt typu String. Podczas wywoływania ReplaceNode
zostanie przywrócony nowy węzeł główny. Większość drzewa składniowego jest dzielona (ponieważ jest niemutowalna), ale węzeł objectCreation
jest zastępowany węzłem memberAccess
, wraz ze wszystkimi węzłami nadrzędnymi aż do korzenia drzewa składniowego.
Wypróbuj poprawkę kodu
Teraz możesz nacisnąć 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 ImmutableArray<int>
. Po naciśnięciu Ctrl+. (okres), zobaczysz poprawkę kodu i zobaczysz automatycznie wygenerowany podgląd różnicy kodu w interfejsie użytkownika żarówki. Roslyn tworzy to dla Ciebie.
Pro Tip: Jeśli uruchomisz drugie wystąpienie programu Visual Studio i nie widzisz ikony ż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 komponentów przez program Visual Studio, dlatego program Visual Studio powinien następnie rozpoznać najnowszy komponent. Najpierw zamknij drugie wystąpienie programu Visual Studio. Następnie w eksploratorze Windowsprzejdź do %LOCALAPPDATA%\Microsoft\VisualStudio\16.0Roslyn\. (Wersja "16.0" zmienia się z wersji na wersję za pomocą programu Visual Studio). Usuń podkatalog ComponentModelCache.
Porozmawiaj przez wideo i dokończ projekt programistyczny
Możesz zobaczyć cały ukończony kod tutaj. Podfoldery DoNotUseImmutableArrayCollectionInitializer i DoNotUseImmutableArrayCtor każdy zawiera plik C# do znajdowania problemów oraz plik C#, który implementuje poprawki kodu wyświetlane w interfejsie lampki w programie Visual Studio. Pamiętaj, że gotowy kod ma nieco więcej abstrakcji, aby uniknąć pobierania obiektu ImmutableArray<T> type object over and over. Używa zagnieżdżonych zarejestrowanych akcji, aby zapisać obiekt określonego typu w kontekście dostępnym za każdym razem, gdy podrzędne akcje (analizowanie tworzenia obiektów i analizowanie inicjalizacji kolekcji) są wykonywane.