Aracılığıyla paylaş


System.CommandLine 2.0.0-beta5+ geçiş kılavuzu

2.0.0-beta5 sürümünün ana odağı API'leri geliştirmek ve kararlı bir sürümünü System.CommandLineyayınlamak için bir adım atmaktı. API'ler basitleştirilmiş ve Çerçeve tasarım yönergeleriyle daha uyumlu ve tutarlı hale getirilmiştir. Bu makalede, 2.0.0-beta5 ve 2.0.0-beta7'de yapılan önemli değişiklikler ve bu değişikliklerin arkasındaki nedenler açıklanmaktadır.

Yeniden Adlandırma

2.0.0-beta4 sürümünde, tüm türler ve üyeler adlandırma yönergelerini izlemedi. Bazıları Boole özellikleri için ön ek kullanma Is gibi adlandırma kurallarıyla tutarlı değildi. 2.0.0-beta5 sürümünde bazı türler ve üyeler yeniden adlandırıldı. Aşağıdaki tabloda eski ve yeni adlar gösterilmektedir:

Eski ad Yeni ad
System.CommandLine.Parsing.Parser CommandLineParser
System.CommandLine.Parsing.OptionResult.IsImplicit Implicit
System.CommandLine.Option.IsRequired Required
System.CommandLine.Symbol.IsHidden Hidden
System.CommandLine.Option.ArgumentHelpName HelpName
System.CommandLine.Parsing.OptionResult.Token IdentifierToken
System.CommandLine.Parsing.ParseResult.FindResultFor GetResult
System.CommandLine.Parsing.SymbolResult.ErrorMessage AddError(String)

† Aynı simge için birden çok hatanın bildirilmesine izin vermek için özelliği ErrorMessage bir yönteme dönüştürüldü ve olarak yeniden adlandırıldı AddError.

Seçenekler ve doğrulayıcılardan oluşan değiştirilebilir koleksiyonlar

Sürüm 2.0.0-beta4,bağımsız değişkenler, seçenekler, alt komutlar, doğrulayıcılar ve tamamlamalar gibi koleksiyonlara öğe eklemek için kullanılan çok sayıda Add yönteme sahipti. Bu koleksiyonlardan bazıları özellikler aracılığıyla salt okunur koleksiyonlar olarak kullanıma sunuldu. Bu nedenle, bu koleksiyonlardan öğeleri kaldırmak mümkün değildi.

2.0.0-beta5 sürümünde, API'ler yöntemler ve (bazen) salt okunur koleksiyonlar yerine Add değiştirilebilir koleksiyonları kullanıma gösterecek şekilde değiştirildi. Bu, öğeleri eklemenizi veya listelemenizi değil, aynı zamanda kaldırmanızı da sağlar. Aşağıdaki tabloda eski yöntem ve yeni özellik adları gösterilmektedir:

Eski yöntem adı Yeni özellik
Command.AddArgument Command.Arguments.Add
Command.AddOption Command.Options.Add
Command.AddCommand Command.Subcommands.Add
Command.AddValidator Command.Validators.Add
Option.AddValidator Option.Validators.Add
Argument.AddValidator Argument.Validators.Add
Command.AddCompletions Command.CompletionSources.Add
Option.AddCompletions Option.CompletionSources.Add
Argument.AddCompletions Argument.CompletionSources.Add
Command.AddAlias Command.Aliases.Add
Option.AddAlias Option.Aliases.Add

RemoveAlias Özelliği artık değiştirilebilir bir koleksiyon olduğundan HasAlias ve Aliases yöntemleri de kaldırıldı. Yöntemi, bir takma adı koleksiyondan kaldırmak için kullanabilirsiniz Remove. Bir takma adın var olup olmadığını denetlemek için Contains yöntemini kullanın.

İsimler ve takma adlar

2.0.0-beta5 sürümünden önce, bir simgenin adı ve diğer adları arasında net bir ayrım yoktu. name oluşturucu için sağlanmadığında, simge adı Option<T>, -- veya - gibi ön ekler kaldırılarak en uzun diğer ad olarak rapor edildi. Bu kafa karıştırıcıydı.

Ayrıca, ayrıştırılan değeri almak için bir seçenek veya bağımsız değişkene referans depolayıp, ardından bunu ParseResult'den değeri almak için kullanmanız gerekiyordu.

Basitliği ve açıklığı artırmak için, bir simgenin adı artık her sembol oluşturucu (dahil) Argument<T>için zorunlu bir parametredir. Ad ve diğer ad kavramı artık ayrıdır: diğer adlar yalnızca diğer adlardır ve simgenin adını içermez. Elbette isteğe bağlıdır. Sonuç olarak, aşağıdaki değişiklikler yapıldı:

  • name artık Argument<T>, Option<T> ve Command 'ün her bir ortak oluşturucusu için zorunlu bir bağımsız değişkendir. durumunda Argument<T>, ayrıştırma için değil, yardımı oluşturmak için kullanılır. ve Option<T>durumundaCommand, ayrıştırma sırasında simgeyi tanımlamak ve ayrıca yardım ve tamamlamalar için kullanılır.
  • Symbol.Name Özelliği artık virtualdeğil; artık salt okunurdur ve simge oluşturulduğunda sağlandığı gibi adı döndürür. Bu nedenle, Symbol.DefaultName kaldırıldı ve Symbol.Name artık en uzun addan --, - veya / ya da başka bir ön eki kaldırmaz.
  • Aliases ve Option tarafından kullanıma sunulan Command özelliği artık değiştirilebilir bir koleksiyondur. Bu koleksiyon artık simgenin adını içermiyor.
  • System.CommandLine.Parsing.IdentifierSymbol kaldırıldı (hem Command hem de Option için bir temel türdü).

Adın her zaman mevcut olması , ayrıştırılan değeri ada göre almanızı sağlar:

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

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

Takma adlarla seçenekler oluşturma

Geçmişte, Option<T> adını kabul eden bazıları dahil olmak üzere birçok oluşturucu kullanıma sundu. Ad artık zorunlu olduğundan ve için sık sık diğer adlar sağlanacağından Option<T>, yalnızca tek bir oluşturucu vardır. Adı ve bir params takma adlar dizisini kabul eder.

2.0.0-beta5'ten önce, Option<T> bir ad ve bir açıklama alan bir oluşturucuya sahipti. Bu nedenle, ikinci bağımsız değişken artık açıklama yerine takma ad olarak değerlendirilebilir. Api'de derleyici hatasına neden olmayan bilinen tek hata değişikliğidir.

Bir açıklamayı yapıcıya geçiren kodu, bir ad ve diğer adlar alan yeni yapıcıyı kullanacak şekilde güncelleyin ve ardından Description özelliğini ayrı olarak ayarlayın. Örneğin:

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."
};

Varsayılan değerler ve özel ayrıştırma

2.0.0-beta4 sürümünde, yöntemleri kullanarak SetDefaultValue seçenekler ve bağımsız değişkenler için varsayılan değerleri ayarlayabilirsiniz. Bu yöntemler, güvenli olmayan bir object değer kabul etti ve değer seçenek veya bağımsız değişken türüyle uyumlu değilse çalışma zamanı hatalarına yol açabilir:

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

Ayrıca ve Option oluşturucularından bazıları, temsilcinin Argument özel ayrıştırıcı mı yoksa varsayılan değer sağlayıcısı mı olduğunu belirten bir ayrıştırma temsilcisiparse () ve Boole değeri (isDefault) kabul etti ve bu da kafa karıştırıcıydı.

Option<T> ve Argument<T> sınıflar artık seçenek veya bağımsız değişken için varsayılan değeri almak üzere çağrılabilen bir temsilci ayarlamak için kullanabileceğiniz bir DefaultValueFactory özelliğe sahiptir. Bu temsilci, seçenek veya bağımsız değişken ayrıştırılmış komut satırı girişinde bulunamadığında çağrılır.

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

Argument<T> ve Option<T>, simge için özel bir ayrıştırıcı ayarlamak amacıyla kullanabileceğiniz bir CustomParser özelliğiyle birlikte gelir.

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

Ayrıca, CustomParser, önceki Func<ParseResult,T> yerine ParseArgument türünde bir delege kabul eder. API'yi basitleştirmek ve API tarafından sunulan türlerin sayısını azaltmak, dolayısıyla JIT derlemesi sırasında harcanan başlangıç süresini düşürmek için bu ve diğer birkaç özel temsilci kaldırıldı.

Daha fazla örnek için, DefaultValueFactory ve CustomParser nasıl kullanılır hakkında bilgi edinmek için System.CommandLine içinde ayrıştırmayı ve doğrulamayı özelleştirme bölümüne bakın.

Ayrıştırma ve çağırmanın ayrılması

2.0.0-beta4 sürümünde komutları ayrıştırma ve çağırma işlemleri birbirinden ayırmak mümkündü, ancak bunun nasıl gerçekleştirildiği açık değildi. Commandbir Parse yöntemi kullanıma sunmadı, ancak CommandExtensions için Parse, Invokeve InvokeAsync uzantı yöntemleri sağladıCommand. Hangi yöntemin ve ne zaman kullanılacağı net olmadığından bu durum kafa karıştırıcıydı. API'yi basitleştirmek için aşağıdaki değişiklikler yapıldı:

  • Command şimdi bir Parse nesnesi döndüren ParseResult metodunu açığa çıkarır. Bu yöntem, komut satırı girişini ayrıştırmak ve ayrıştırma işleminin sonucunu döndürmek için kullanılır. Ayrıca, komutun çağrılmadığını ancak yalnızca eşzamanlı bir şekilde ayrıştırıldığını açıkça gösterir.
  • ParseResultşimdi komutunu çağırmak için kullanabileceğiniz hem hem Invoke de InvokeAsync yöntemlerini kullanıma sunar. Bu kalıp, komutun ayrıştırıldıktan sonra gerçekleştirilmesini ve hem zaman uyumlu hem de zaman uyumsuz olarak çağrılmasını mümkün kılar.
  • Artık CommandExtensions gerekli olmadığından sınıf kaldırıldı.

Konfigürasyon

2.0.0-beta5 sürümünden önce, ayrıştırma özelleştirmek mümkündü, ancak yalnızca bazı genel Parse yöntemlerle. İki ortak oluşturucu sunan bir Parser sınıf vardı: biri bir Command kabul ediyor ve diğeri ise bir CommandLineConfiguration kabul ediyor. CommandLineConfiguration değiştirilemezdi ve onu oluşturmak için CommandLineBuilder sınıfı tarafından sunulan bir tasarım deseni kullanmanız gerekiyordu. API'yi basitleştirmek için aşağıdaki değişiklikler yapıldı:

  • CommandLineConfiguration iki değiştirilebilir sınıfa ayrılmıştır (2.0.0-beta7 sürümünde): ParserConfiguration ve InvocationConfiguration. Çağırma yapılandırması oluşturmak, artık InvocationConfiguration örneğini oluşturmak ve özelleştirmek istediğiniz özellikleri ayarlamak kadar basittir.
  • Her Parse yöntem artık ayrıştırma işlemini özelleştirmek için kullanabileceğiniz isteğe bağlı ParserConfiguration bir parametreyi kabul eder. Alternatif sağlanmadığında, varsayılan yapılandırma kullanılır.
  • Ad çakışmalarını önlemek için, Parser diğer ayrıştırıcı türlerinden ayırmak amacıyla CommandLineParser olarak yeniden adlandırıldı. Durum bilgisi olmadığından artık yalnızca statik yöntemlere sahip statik bir sınıftır. İki Parse ayrıştırma yöntemini kullanıma sunar: biri bir IReadOnlyList<string> args öğesini kabul eder ve diğeri bir string args kabul eder. İkincisi, CommandLineParser.SplitCommandLine(String) (ayrıca genel) kullanarak, komut satırı girişini ayrıştırmadan önce belirteçlere böler.

CommandLineBuilderExtensions de kaldırıldı. Yöntemlerini yeni API'lerle şu şekilde eşleyebilirsiniz:

  • CancelOnProcessTermination, artık InvocationConfiguration adlı ProcessTerminationTimeout özelliğidir. Varsayılan olarak 2 saniyelik zaman aşımı ile etkinleştirilir. Devre dışı bırakmak için null olarak ayarla. Daha fazla bilgi için bkz . İşlem sonlandırma zaman aşımı.

  • EnableDirectives, UseEnvironmentVariableDirective, UseParseDirectiveve UseSuggestDirective kaldırıldı. Yeni bir Yönerge türü kullanıma sunulmuştur ve RootCommand artık bir Directives özelliği kullanıma sunar. Bu koleksiyonu kullanarak yönergeleri ekleyebilir, kaldırabilir ve yineleyebilirsiniz. Öneri yönergesi varsayılan olarak dahil edilir; DiagramDirective veya EnvironmentVariablesDirectivegibi diğer yönergeleri de kullanabilirsiniz.

  • EnableLegacyDoubleDashBehavior Kaldırıldı. Eşleşmeyen tüm belirteçler artık ParseResult.UnmatchedTokens özelliği tarafından ortaya çıkartıldı. Daha fazla bilgi için bkz. Eşleşmeyen belirteçler.

  • EnablePosixBundling Kaldırıldı. Paketleme artık varsayılan olarak etkindir, bunu devre dışı bırakmak için ParserConfiguration.EnablePosixBundling özelliğini false olarak ayarlayabilirsiniz. Daha fazla bilgi için bkz . EnablePosixBundling.

  • RegisterWithDotnetSuggest genellikle uygulama başlatma sırasında pahalı bir işlem gerçekleştirirken kaldırıldı. Şimdi komutlarını dotnet suggest kaydetmeniz gerekir.

  • UseExceptionHandler Kaldırıldı. Varsayılan özel durum işleyicisi artık varsayılan olarak etkindir; özelliğini InvocationConfiguration.EnableDefaultExceptionHandlerolarak ayarlayarak false devre dışı bırakabilirsiniz. Bu, özel durumları, yalnızca Invoke veya InvokeAsync yöntemlerini bir try-catch bloğuna sarmalayarak özel bir şekilde işlemek istediğinizde kullanışlıdır. Daha fazla bilgi için bkz . EnableDefaultExceptionHandler.

  • UseHelp ve UseVersion kaldırıldı. Yardım ve sürüm artık HelpOption ve VersionOption türleri tarafından kullanıma sunuldu. Her ikisi de RootCommand tarafından tanımlanan seçeneklere varsayılan olarak eklenir. Daha fazla bilgi için bkz. Yardım çıkışını özelleştirme ve Sürüm seçeneği.

  • UseHelpBuilder Kaldırıldı. Daha fazla bilgi için bkz. Nasıl yapılır: Yardım çıktısını System.CommandLine özelleştirmek.

  • AddMiddleware Kaldırıldı. Uygulama başlatmayı yavaşlattı ve özellikler onsuz ifade edilebilir.

  • UseParseErrorReporting ve UseTypoCorrections kaldırıldı. Artık ParseResult çağrıldığında ayrıştırma hataları varsayılan olarak bildirilir. Özelliği tarafından ParseErrorAction sunulan eylemi kullanarak ParseResult.Action yapılandırabilirsiniz.

    ParseResult result = rootCommand.Parse("myArgs", config);
    if (result.Action is ParseErrorAction parseError)
    {
        parseError.ShowTypoCorrections = true;
        parseError.ShowHelp = false;
    }
    
  • UseLocalizationResources ve LocalizationResources kaldırıldı. Bu özellik çoğunlukla CLI tarafından dotnet öğesine eksik çeviriler eklemek için kullanılmıştır System.CommandLine. Tüm bu çeviriler kendisine taşındı System.CommandLine , bu nedenle bu özellik artık gerekli değildir. Diliniz için destek eksikse lütfen bir sorun bildirin.

  • UseTokenReplacer Kaldırıldı. Yanıt dosyaları varsayılan olarak etkindir, ancak özelliğini ResponseFileTokenReplacerolarak ayarlayarak null bunları devre dışı bırakabilirsiniz. Yanıt dosyalarının nasıl işlendiğini özelleştirmek için özel bir uygulama da sağlayabilirsiniz.

Son olarak, IConsole ve tüm ilgili arabirimleri (IStandardOut, IStandardError, IStandardIn) kaldırıldı. InvocationConfiguration iki TextWriter özelliği kullanıma sunar: Output ve Error. Bu özellikleri, test için çıktı yakalamak amacıyla kullanabileceğiniz TextWriter gibi herhangi bir StringWriter örneğe ayarlayabilirsiniz. Bu değişikliğin nedeni daha az türü ortaya çıkarmak ve mevcut soyutlamaları yeniden kullanmaktı.

Çağrı

2.0.0-beta4 sürümünde, ICommandHandler arabirimi, Invoke ve InvokeAsync yöntemlerini ortaya çıktı ve bu yöntemler ayrıştırılmış komutu çağırmak için kullanıldı. Bu, örneğin bir komut için senkron bir işleyici tanımlayıp ardından asenkron olarak çağırarak (bu da bir kilitlenmeye yol açabilir), senkron ve asenkron kodun karıştırılmasını kolaylaştırdı. Ayrıca, işleyiciyi yalnızca bir komut için tanımlamak mümkündü, ancak bir seçenek (örneğin, yardım seçeneği yardımı görüntüler) veya bir yönerge için değil.

Yeni bir soyut temel sınıf CommandLineAction ve iki türetilmiş sınıf SynchronousCommandLineAction ve AsynchronousCommandLineActiontanıtıldı. İlki çıkış int kodu döndüren zaman uyumlu eylemler için kullanılırken, ikincisi çıkış kodu döndüren Task<int> zaman uyumsuz eylemler için kullanılır.

Eylem tanımlamak için türetilmiş bir tür oluşturmanız gerekmez. Bir komut için bir eylem ayarlamak üzere Command.SetAction yöntemini kullanabilirsiniz. Zaman uyumlu eylem, System.CommandLine.ParseResult parametresini alan ve bir çıkış kodu döndüren (veya hiçbir şey döndürmeyen ve o zaman varsayılan bir int çıkış kodunun döndüğü) bir temsilci olabilir. Zaman uyumsuz eylem, System.CommandLine.ParseResult ve CancellationToken parametrelerini alan ve bir Task<int> (veya Task varsayılan çıkış kodunu almak için) döndüren bir temsilci olabilir.

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

Geçmişte, CancellationTokenInvokeAsync'e geçirildikten sonra InvocationContext'nin bir yöntemi ile işleyiciye açıktı.

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

Kullanıcıların çoğu bu belirteci alıp daha fazla geçirmiyordu. CancellationToken artık zaman uyumsuz eylemler için zorunlu bir bağımsız değişkendir; böylece derleyici daha fazla geçirilmediğinde bir uyarı üretir (bkz . CA2016).

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

Bu ve yukarıda belirtilen diğer değişiklikler sonucunda sınıf InvocationContext da kaldırıldı. ParseResult artık doğrudan eyleme geçirilir, böylece ayrıştırılan değerlere ve seçeneklere doğrudan buradan erişebilirsiniz.

Bu değişiklikleri özetlemek için:

  • Arabirim ICommandHandler kaldırıldı. SynchronousCommandLineAction ve AsynchronousCommandLineAction tanıtıldı.
  • Command.SetHandler yöntemi olarak yeniden adlandırıldıSetAction.
  • Command.Handler özelliği Command.Action olarak yeniden adlandırıldı. Option ile Option.Actiongenişletilmişti.
  • InvocationContext Kaldırıldı. ParseResult artık doğrudan aksiyona iletilir.

Eylemleri kullanma hakkında daha fazla ayrıntı için bkz. 'de System.CommandLinekomutları ayrıştırma ve çağırma.

Basitleştirilmiş API'nin avantajları

2.0.0-beta5 sürümünde yapılan değişiklikler API'yi daha tutarlı, geleceğe dayanıklı ve mevcut ve yeni kullanıcılar için daha kolay kullanılabilir hale getirir.

Ortak arabirim sayısı 11'den 0'a ve genel sınıflar (ve yapılar) 56'dan 38'e düştüğünden yeni kullanıcıların daha az kavram ve tür öğrenmesi gerekir. Genel yöntem sayısı 378'den 235'e, ortak özellikler ise 118'den 99'a düşürüldü.

System.CommandLine tarafından referans alınan derleme sayısı 11'den 6'ya düşürüldü.

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

Kitaplığın boyutu küçültülmüştür (32%) ve kitaplığı kullanan NativeAOT uygulamalarının boyutu da bu şekildedir.

Basitlik ayrıca kitaplığın performansını da geliştirmiştir (bu, çalışmanın bir yan etkisidir, ana hedefi değildir). Karşılaştırmalar, komutları ayrıştırma ve çağırmanın, özellikle birçok seçenek ve bağımsız değişken içeren büyük komutlar için 2.0.0-beta4 sürümünden daha hızlı olduğunu göstermektedir. Performans iyileştirmeleri hem zaman uyumlu hem de zaman uyumsuz senaryolarda görülebilir.

Daha önce sunulan en basit uygulama aşağıdaki sonuçları üretir:

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 |

Gördüğünüz gibi başlangıç süresi (kıyaslamalar belirli bir yürütülebilir dosyayı çalıştırmak için gereken süreyi rapor eder) 2.0.0-beta4'e kıyasla 12% geliştirildi. Uygulamayı NativeAOT ile derlerseniz, args ayrıştırmayan NativeAOT uygulamasından (yukarıdaki tabloda EmptyAOT) yalnızca 3 ms daha yavaştır. Ayrıca, boş bir uygulamanın (63,58 ms) yükünü dışlarsanız, ayrıştırma 40% 2,0,0-beta4 (22,22 ms ile 13,66 ms) arasında daha hızlıdır.

Ayrıca bakınız