Udostępnij za pośrednictwem


System.CommandLine Przewodnik migracji 2.0.0-beta5

Ważne

System.CommandLine jest obecnie dostępna w wersji zapoznawczej, a ta dokumentacja dotyczy wersji 2.0 beta 5. Niektóre informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany przed jego wydaniem. Firma Microsoft nie udziela żadnych gwarancji, wyraźnych ani domniemanych, w odniesieniu do podanych tutaj informacji.

Głównym celem wydania wersji 2.0.0-beta5 było ulepszenie interfejsów API i podjęcie kroku w kierunku wydania stabilnej wersji programu System.CommandLine. Interfejsy API zostały uproszczone, a także bardziej spójne i zgodne z wytycznymi projektowania Frameworku. W tym artykule opisano zmiany powodujące niezgodność wprowadzone w wersji 2.0.0-beta5 oraz ich przyczyny.

Zmienianie nazw

W wersji 2.0.0-beta4 nie wszystkie typy i elementy były zgodne z wytycznymi dotyczącymi nazewnictwa. Niektóre z nich nie były zgodne z konwencjami nazewnictwa, takimi jak używanie prefiksu Is dla właściwości typu Boolean. W wersji 2.0.0-beta5 zmieniono nazwy niektórych typów i członków. W poniższej tabeli przedstawiono stare i nowe nazwy:

Stara nazwa Nowa nazwa
System.CommandLine.Parsing.Parser System.CommandLine.Parsing.CommandLineParser
System.CommandLine.Parsing.OptionResult.IsImplicit System.CommandLine.Parsing.OptionResult.Implicit
System.CommandLine.Option.IsRequired System.CommandLine.Option.Required
System.CommandLine.Symbol.IsHidden System.CommandLine.Symbol.Hidden
System.CommandLine.Option.ArgumentHelpName System.CommandLine.Option.HelpName
System.CommandLine.Parsing.OptionResult.Token System.CommandLine.Parsing.OptionResult.IdentifierToken
System.CommandLine.Parsing.ParseResult.FindResultFor System.CommandLine.Parsing.ParseResult.GetResult
System.CommandLine.Parsing.SymbolResult.ErrorMessage System.CommandLine.Parsing.SymbolResult.AddError

Aby umożliwić raportowanie wielu błędów dla tego samego symbolu, ErrorMessage właściwość została przekonwertowana na metodę i zmieniono jej nazwę na AddError.

Eksponowanie kolekcji modyfikowalnych

Wersja 2.0.0-beta4 miała wiele Add metod, które były używane do dodawania elementów do kolekcji, takich jak argumenty, opcje, polecenia podrzędne, moduły sprawdzania poprawności i uzupełniania. Niektóre z tych kolekcji zostały ujawnione za pośrednictwem właściwości jako kolekcji tylko do odczytu. Z tego powodu nie można było usunąć elementów z tych kolekcji.

W wersji 2.0.0-beta5 zmieniliśmy API, aby udostępniać kolekcje modyfikowalne zamiast metod Add i (czasami) kolekcji tylko do odczytu. Pozwala to nie tylko dodawać elementy lub wyliczać je, ale także je usuwać. W poniższej tabeli przedstawiono starą metodę i nowe nazwy właściwości:

Stara nazwa metody Nowa właściwość
System.CommandLine.Command.AddArgument System.CommandLine.Command.Arguments.Add
System.CommandLine.Command.AddOption System.CommandLine.Command.Options.Add
System.CommandLine.Command.AddCommand System.CommandLine.Command.Subcommands.Add
System.CommandLine.Command.AddValidator System.CommandLine.Command.Validators.Add
System.CommandLine.Option.AddValidator System.CommandLine.Option.Validators.Add
System.CommandLine.Argument.AddValidator System.CommandLine.Argument.Validators.Add
System.CommandLine.Command.AddCompletions System.CommandLine.Command.CompletionSources.Add
System.CommandLine.Option.AddCompletions System.CommandLine.Option.CompletionSources.Add
System.CommandLine.Argument.AddCompletions System.CommandLine.Argument.CompletionSources.Add
System.CommandLine.Command.AddAlias System.CommandLine.Command.Aliases.Add
System.CommandLine.Option.AddAlias System.CommandLine.Option.Aliases.Add

Metody RemoveAlias i HasAlias zostały również usunięte, ponieważ właściwość Aliases jest teraz kolekcją modyfikowalną. Możesz użyć Remove metody , aby usunąć alias z kolekcji. Contains Użyj metody , aby sprawdzić, czy istnieje alias.

Nazwy i aliasy

Przed wersją 2.0.0-beta5 nie było wyraźnego rozdzielenia nazwy i aliasów symbolu. Jeśli nie podano name dla konstruktora Option<T>, symbol zgłaszał swoją nazwę jako najdłuższy alias z prefiksami, takimi jak --, - lub /, usuniętymi. To było dezorientujące.

Ponadto, aby uzyskać przeanalizowaną wartość, użytkownicy musieli przechowywać odwołanie do opcji lub argumentu, a następnie użyć go do pobrania wartości z ParseResult.

Aby podwyższyć prostotę i jawność, nazwa symbolu jest teraz obowiązkowym parametrem dla każdego konstruktora symboli (w tym Argument<T>). Oddzieliliśmy również koncepcję nazwy i aliasów; teraz aliasy są tylko aliasami i nie zawierają nazwy symbolu. Oczywiście są one opcjonalne. W rezultacie wprowadzono następujące zmiany:

  • name jest teraz obowiązkowym argumentem dla każdego publicznego konstruktora Argument<T>, Option<T>i Command. W przypadku Argument<T> nie jest używany do analizowania, ale w celu wygenerowania pomocy. W przypadku elementów Option<T> i Commandsłuży do identyfikowania symbolu podczas analizowania, a także w celu uzyskania pomocy i ukończenia.
  • Właściwość Symbol.Name nie jest już virtual; jest teraz tylko do odczytu i zwraca nazwę podaną podczas tworzenia symbolu. W związku z tym Symbol.DefaultName został usunięty i Option.Name nie usuwa już prefiksu --, -, / ani żadnego innego prefiksu z najdłuższego aliasu.
  • Właściwość Aliases ujawniona przez Option i Command jest teraz kolekcją modyfikowalną. Ta kolekcja nie zawiera już nazwy symbolu.
  • System.CommandLine.Parsing.IdentifierSymbol został usunięty (był to typ podstawowy dla obu Command i Option).

Posiadanie nazwy zawsze obecnej umożliwia uzyskanie przeanalizowanej wartości według nazwy:

RootCommand command = new("The description.")
{
    new Option<int>("--number")
};

ParseResult parseResult = command.Parse(args);
int number = parseResult.GetValue<int>("--number");

Tworzenie opcji za pomocą aliasów

W przeszłości Option<T> uwidocznił wiele konstruktorów, z których niektóre zaakceptowały nazwę. Ponieważ nazwa jest teraz obowiązkowa i oczekujemy, że aliasy będą często podawane dla Option<T>, istnieje tylko jeden konstruktor. Akceptuje nazwę i tablicę params aliasów.

Przed wersją 2.0.0-beta5, Option<T> miał konstruktor, który przyjmował nazwę i opis. Z tego powodu drugi argument może być teraz traktowany jako alias, a nie opis. Jest to jedyna znana zmiana łamiąca zgodność w interfejsie API, która nie spowoduje błędu kompilatora.

Stary kod, który używał konstruktora z opisem, powinien zostać zaktualizowany, aby użyć nowego konstruktora, który przyjmuje nazwę i aliasy, a następnie ustawić Description właściwość oddzielnie. Przykład:

Option<bool> beta4 = new("--help", "An option with aliases.");
beta4b.Aliases.Add("-h");
beta4b.Aliases.Add("/h");

Option<bool> beta5 = new("--help", "-h", "/h")
{
    Description = "An option with aliases."
};

Wartości domyślne i analizowanie niestandardowe

W wersji 2.0.0-beta4 użytkownicy mogą ustawić wartości domyślne opcji i argumentów przy użyciu SetDefaultValue metod . Metody te przyjmowały wartość object, która była niezgodna z typem i mogła prowadzić do błędów czasu wykonywania, jeśli wartość nie była zgodna z opcją lub typem argumentu.

Option<int> option = new("--number");
option.SetDefaultValue("text"); // This is not type-safe, as the value is a string, not an int.

Ponadto niektóre konstruktory Option i Argument przyjmowały delegata do analizy i wartość logiczną wskazującą, czy delegat był niestandardowym analizatorem, czy domyślnym dostawcą wartości. To było mylące.

Option<T> i Argument<T> klasy mają teraz właściwość DefaultValueFactory, która może służyć do ustawiania delegata, którego można wywołać, aby uzyskać wartość domyślną dla opcji lub argumentu. Ten delegat jest wywoływany, gdy opcja lub argument nie zostanie znaleziony w przeanalizowanych danych wejściowych wiersza polecenia.

Option<int> number = new("--number")
{
    DefaultValueFactory = _ => 42
};

Argument<T> i Option<T> także mają właściwość CustomParser, która może służyć do ustawienia niestandardowego parsera dla symbolu:

Argument<Uri> uri = new("arg")
{
    CustomParser = result =>
    {
        if (!Uri.TryCreate(result.Tokens.Single().Value, UriKind.RelativeOrAbsolute, out var uriValue))
        {
            result.AddError("Invalid URI format.");
            return null;
        }

        return uriValue;
    }
};

CustomParser Ponadto akceptuje delegata typu Func<ParseResult, T>, a nie poprzedniego ParseArgument delegata. Ten i kilka innych delegatów niestandardowych zostały usunięte, aby uprościć interfejs API i zmniejszyć liczbę typów uwidocznionych przez interfejs API, co skraca czas uruchamiania spędzony podczas kompilacji JIT.

Aby uzyskać więcej przykładów używania poleceń DefaultValueFactory i CustomParser, zobacz Jak dostosować analizowanie i walidację w programie System.CommandLine.

Rozdzielenie analizy składniowej i wywołania

W wersji 2.0.0-beta4 można było oddzielić analizowanie i wywoływanie poleceń, ale nie było jasne, jak to zrobić. Command nie udostępnił metody Parse, ale CommandExtensions dostarczył metody rozszerzenia Parse, Invoke i InvokeAsync dla elementu Command. Było to mylące, ponieważ nie było jasne, której metody użyć i kiedy. Wprowadzono następujące zmiany w celu uproszczenia interfejsu API:

  • Command teraz uwidacznia metodę Parse zwracającą ParseResult obiekt. Ta metoda służy do analizowania danych wejściowych wiersza polecenia i zwracania wyniku operacji analizy. Co więcej, jasno widać, że polecenie nie jest wywoływane, ale tylko analizowane i tylko w sposób synchroniczny.
  • ParseResult teraz uwidacznia metody Invoke i InvokeAsync , które mogą służyć do wywoływania polecenia. Dzięki temu polecenie jest wywoływane po przeanalizowaniu i umożliwia wywołanie zarówno synchroniczne, jak i asynchroniczne.
  • Klasa została usunięta CommandExtensions , ponieważ nie jest już potrzebna.

Konfiguracja

Przed wersją 2.0.0.0-beta5 można było dostosować analizowanie, ale tylko przy użyciu niektórych metod publicznych Parse . Istniała Parser klasa, która uwidoczniła dwa publiczne konstruktory: jeden akceptujący element Command i drugi akceptujący element CommandLineConfiguration. CommandLineConfiguration był niezmienny i aby go utworzyć, trzeba było użyć wzorca konstruktora uwidocznionego przez klasę CommandLineBuilder . Wprowadzono następujące zmiany w celu uproszczenia interfejsu API:

  • CommandLineConfiguration został uczyniony modyfikowalnym, a CommandLineBuilder został usunięty. Tworzenie konfiguracji jest teraz tak proste, jak utworzenie wystąpienia CommandLineConfiguration i ustawienie właściwości, które chcesz dostosować. Ponadto utworzenie nowego wystąpienia konfiguracji jest odpowiednikiem wywołania metody CommandLineBuilder dla UseDefaults.
  • Każda Parse metoda akceptuje teraz opcjonalny CommandLineConfiguration parametr, który może służyć do dostosowywania analizy. Jeśli nie zostanie podana, zostanie użyta konfiguracja domyślna.
  • Parser zmieniono nazwę na CommandLineParser w celu odróżnienia od innych typów analizatorów, aby uniknąć konfliktów nazw. Ponieważ jest bezstanowa, jest to teraz klasa statyczna z tylko metodami statycznymi. Udostępnia dwie metody analizy: jedną akceptującą Parse, a drugą akceptującą IReadOnlyList<string> args. Ten ostatni używa CommandLineParser.SplitCommandLine (również publicznej) do podzielenia danych wejściowych wiersza polecenia na tokeny przed ich przeanalizowaniem.

CommandLineBuilderExtensions został również usunięty. Poniżej przedstawiono sposób mapowania jej metod na nowe interfejsy API:

  • CancelOnProcessTermination jest teraz właściwością CommandLineConfiguration o nazwie ProcessTerminationTimeout. Jest ona domyślnie włączona z limitem czasu 2s. Ustaw ją na wartość null , aby ją wyłączyć.

  • EnableDirectives, , UseEnvironmentVariableDirectiveUseParseDirectivei UseSuggestDirective zostały usunięte. Wprowadzono nowy typ dyrektywy, a RootCommand teraz ujawnia właściwość System.CommandLine.RootCommand.Directives. Za pomocą tej kolekcji można dodawać, usuwać i iterować dyrektywy. Sugerowana dyrektywa jest domyślnie dołączana; Można również użyć innych dyrektyw, takich jak DiagramDirective lub EnvironmentVariablesDirective.

  • EnableLegacyDoubleDashBehavior został usunięty. Wszystkie niedopasowane tokeny są teraz udostępniane przez właściwość ParseResult.UnmatchedTokens .

  • EnablePosixBundling został usunięty. Tworzenie pakietów jest teraz domyślnie włączone, można ją wyłączyć, ustawiając właściwość CommandLineConfiguration.EnableBundling na false.

  • RegisterWithDotnetSuggest został usunięty, ponieważ wykonał kosztowną operację, zwykle podczas uruchamiania aplikacji. Teraz musisz zarejestrować polecenia dotnet suggestręcznie.

  • UseExceptionHandler został usunięty. Domyślna procedura obsługi wyjątków jest teraz domyślnie włączona, można ją wyłączyć, ustawiając właściwość CommandLineConfiguration.EnableDefaultExceptionHandler na falsewartość . Jest to przydatne, gdy chcesz obsługiwać wyjątki w niestandardowy sposób, po prostu opakowując metody Invoke lub InvokeAsync w blok try-catch.

  • UseHelp i UseVersion zostały usunięte. Pomoc i wersja są teraz udostępniane przez typy publiczne HelpOption i VersionOption . Są one domyślnie uwzględniane w opcjach zdefiniowanych przez RootCommand.

  • UseHelpBuilder został usunięty. Aby uzyskać więcej informacji na temat dostosowywania danych wyjściowych pomocy, zobacz Jak dostosować pomoc w programie System.CommandLine.

  • AddMiddleware został usunięty. Spowolniło to uruchamianie aplikacji, a funkcje można zrealizować bez tego.

  • UseParseErrorReporting i UseTypoCorrections zostały usunięte. Błędy analizy są teraz domyślnie zgłaszane podczas wywoływania ParseResult. Można ją skonfigurować, korzystając z właściwości udostępnionej przez ParseErrorAction w ParseResult.Action.

    ParseResult result = rootCommand.Parse("myArgs", config);
    if (result.Action is ParseErrorAction parseError)
    {
        parseError.ShowTypoCorrections = true;
        parseError.ShowHelp = false;
    }
    
  • UseLocalizationResources i LocalizationResources zostały usunięte. Ta funkcja była używana głównie przez konsolę dotnet do dodania brakujących tłumaczeń do System.CommandLine. Wszystkie te tłumaczenia zostały przeniesione bezpośrednio do System.CommandLine, więc ta funkcja nie jest już potrzebna. Jeśli brakuje obsługi twojego języka, zgłoś problem.

  • UseTokenReplacer został usunięty. Pliki odpowiedzi są domyślnie włączone, ale można je wyłączyć, ustawiając System.CommandLine.CommandLineConfiguration.ResponseFileTokenReplacer właściwość na null. Możesz również udostępnić niestandardową implementację, aby dostosować sposób przetwarzania plików odpowiedzi.

Ostatnia, ale nie mniej ważna, usunięto IConsole oraz wszystkie powiązane interfejsy (IStandardOut, IStandardError, IStandardIn). System.CommandLine.CommandLineConfiguration Uwidacznia dwie TextWriter właściwości: Output i Error. Można je ustawić na dowolne TextWriter wystąpienie, na przykład StringWriter, które można wykorzystać do przechwytywania wyników na potrzeby testów. Naszą motywacją było uwidocznienie mniejszej liczby typów i ponowne użycie istniejących abstrakcji.

Wywołanie

W wersji 2.0.0-beta4 interfejs ICommandHandler uwidocznił metody Invoke i InvokeAsync, które zostały użyte do wywołania przeanalizowanego polecenia. Ułatwiło to połączenie synchronicznego i asynchronicznego kodu, na przykład przez zdefiniowanie synchronicznej procedury obsługi dla polecenia, a następnie wywołanie go asynchronicznie (co może prowadzić do zakleszczenia). Ponadto można było zdefiniować program obsługi tylko dla polecenia, ale nie dla opcji (takiej jak pomoc, która wyświetla pomoc) lub dyrektywy.

Nowa abstrakcyjna klasa System.CommandLine.CommandLineActionbazowa i dwie klasy pochodne: System.CommandLine.SynchronousCommandLineAction i System.CommandLine.AsynchronousCommandLineAction zostały wprowadzone. Pierwszy jest używany do synchronicznych akcji, które zwracają int kod zakończenia, podczas gdy ten ostatni jest używany do asynchronicznych akcji, które zwracają Task<int> kod zakończenia.

Nie musisz tworzyć typu pochodnego, aby zdefiniować akcję. Możesz użyć System.CommandLine.Command.SetAction metody , aby ustawić akcję dla polecenia. Akcja synchroniczna może być delegatem System.CommandLine.ParseResult przyjmującym parametr i zwracającym kod zakończenia int (lub nic, wtedy zwracany jest domyślny kod zakończenia 0). Akcja asynchroniczna może być delegatem, który przyjmuje parametry System.CommandLine.ParseResult i CancellationToken, i zwraca wartość typu Task<int> (lub Task, aby uzyskać domyślny kod zakończenia).

rootCommand.SetAction(ParseResult parseResult =>
{
    FileInfo parsedFile = parseResult.GetValue(fileOption);
    ReadFile(parsedFile);
});

W przeszłości element CancellationToken przekazany do InvokeAsync został dostępny dla procedury obsługi poprzez metodę InvocationContext.

rootCommand.SetHandler(async (InvocationContext context) =>
{
    string? urlOptionValue = context.ParseResult.GetValueForOption(urlOption);
    var token = context.GetCancellationToken();
    returnCode = await DoRootCommand(urlOptionValue, token);
});

Większość naszych użytkowników nie uzyskała tego tokenu i nie przekazywała go dalej. Wprowadziliśmy CancellationToken obowiązkowy argument dla akcji asynchronicznych, aby kompilator wygenerował ostrzeżenie, gdy nie zostanie przekazane dalej (CA2016).

rootCommand.SetAction((ParseResult parseResult, CancellationToken token) =>
{
    string? urlOptionValue = parseResult.GetValue(urlOption);
    return DoRootCommandAsync(urlOptionValue, token);
});

W wyniku tych i innych zmian, które zostały wymienione, klasa została również usunięta InvocationContext . Element ParseResult jest teraz przekazywany bezpośrednio do akcji, dzięki czemu można uzyskać dostęp do analizowanych wartości i opcji bezpośrednio z niej.

Aby podsumować te zmiany:

  • Interfejs ICommandHandler został usunięty. SynchronousCommandLineAction i AsynchronousCommandLineAction zostały wprowadzone.
  • Nazwa Command.SetHandler metody została zmieniona na SetAction.
  • Nazwa Command.Handler właściwości została zmieniona na Command.Action. Option został rozszerzony o Option.Action.
  • InvocationContext został usunięty. Element ParseResult jest teraz przekazywany bezpośrednio do akcji.

Aby uzyskać więcej informacji na temat używania akcji, zobacz Jak analizować i wywoływać polecenia w programie System.CommandLine.

Zalety uproszczonego interfejsu API

Mamy nadzieję, że zmiany wprowadzone w wersji 2.0.0-beta5 sprawią, że interfejs API będzie bardziej spójny, niezawodny i łatwiejszy w użyciu dla istniejących i nowych użytkowników.

Nowi użytkownicy muszą nauczyć się mniej pojęć i typów, ponieważ liczba interfejsów publicznych spadła z 11 do 0, a klasy publiczne (i struktury) spadły z 56 do 38. Liczba metod publicznych spadła z 378 do 235, a właściwości publiczne z 118 do 99.

Liczba zestawów, System.CommandLine do których się odwołuje, jest ograniczona z 11 do 6:

System.Collections
- System.Collections.Concurrent
- System.ComponentModel
System.Console
- System.Diagnostics.Process
System.Linq
System.Memory
- System.Net.Primitives
System.Runtime
- System.Runtime.Serialization.Formatters
+ System.Runtime.InteropServices
- System.Threading

Pozwoliło nam to zmniejszyć rozmiar biblioteki o 32% i rozmiar następującej aplikacji NativeAOT o 20%:

Option<bool> boolOption = new Option<bool>(new[] { "--bool", "-b" }, "Bool option");
Option<string> stringOption = new Option<string>(new[] { "--string", "-s" }, "String option");

RootCommand command = new RootCommand
{
    boolOption,
    stringOption
};

command.SetHandler<bool, string>(Run, boolOption, stringOption);

return new CommandLineBuilder(command).UseDefaults().Build().Invoke(args);

static void Run(bool boolean, string text)
{
    Console.WriteLine($"Bool option: {text}");
    Console.WriteLine($"String option: {boolean}");
}
Option<bool> boolOption = new Option<bool>("--bool", "-b") { Description = "Bool option" };
Option<string> stringOption = new Option<string>("--string", "-s") { Description = "String option" };

RootCommand command = new ()
{
    boolOption,
    stringOption,
};

command.SetAction(parseResult => Run(parseResult.GetValue(boolOption), parseResult.GetValue(stringOption)));

return new CommandLineConfiguration(command).Invoke(args);

static void Run(bool boolean, string text)
{
    Console.WriteLine($"Bool option: {text}");
    Console.WriteLine($"String option: {boolean}");
}

Prostota poprawiła również wydajność biblioteki (jest to efekt uboczny pracy, a nie główny cel). Testy porównawcze pokazują, że analizowanie i wywoływanie poleceń jest teraz szybsze niż w wersji 2.0.0-beta4, zwłaszcza w przypadku dużych poleceń z wieloma opcjami i argumentami. Ulepszenia wydajności są widoczne zarówno w scenariuszach synchronicznych, jak i asynchronicznych.

W przypadku najprostszej aplikacji przedstawionej wcześniej uzyskaliśmy następujące wyniki:

BenchmarkDotNet v0.15.0, Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley)
AMD Ryzen Threadripper PRO 3945WX 12-Cores 3.99GHz, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.300
  [Host]     : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
  Job-JJVAFK : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2

EvaluateOverhead=False  OutlierMode=DontRemove  InvocationCount=1
IterationCount=100  UnrollFactor=1  WarmupCount=3

| Method                  | Args           | Mean      | StdDev   | Ratio |
|------------------------ |--------------- |----------:|---------:|------:|
| Empty                   | --bool -s test |  63.58 ms | 0.825 ms |  0.83 |
| EmptyAOT                | --bool -s test |  14.39 ms | 0.507 ms |  0.19 |
| SystemCommandLineBeta4  | --bool -s test |  85.80 ms | 1.007 ms |  1.12 |
| SystemCommandLineNow    | --bool -s test |  76.74 ms | 1.099 ms |  1.00 |
| SystemCommandLineNowR2R | --bool -s test |  69.35 ms | 1.127 ms |  0.90 |
| SystemCommandLineNowAOT | --bool -s test |  17.35 ms | 0.487 ms |  0.23 |

Jak widać, czas uruchamiania (testy porównawcze zgłaszają czas wymagany do uruchomienia danego pliku wykonywalnego) poprawił się o 12% w porównaniu z 2.0.0.0-beta4. Jeśli skompilujemy aplikację z funkcją NativeAOT, jest to tylko 3 ms wolniejsze niż aplikacja NativeAOT, która w ogóle nie analizuje args (EmptyAOT w powyższej tabeli). Ponadto, gdy wykluczamy obciążenie pustej aplikacji (63,58 ms), analizowanie jest 40% szybsze niż w wersji 2.0.0-beta4 (22.22 ms vs 13.66 ms).

Zobacz także