Bagikan melalui


Cara mengikat argumen ke penangananSystem.CommandLine

Penting

System.CommandLine saat ini dalam PRATINJAU, dan dokumentasi ini untuk versi 2.0 beta 4. Beberapa informasi terkait produk prarilis yang dapat diubah secara signifikan sebelum dirilis. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Proses mengurai argumen dan menyediakan argumen ke kode penangan perintah disebut pengikatan parameter. System.CommandLine memiliki kemampuan untuk mengikat banyak jenis argumen bawaan. Misalnya, bilangan bulat, enum, dan objek sistem file seperti FileInfo dan DirectoryInfo dapat terikat. Beberapa System.CommandLine jenis juga dapat terikat.

Validasi argumen bawaan

Argumen memiliki jenis dan peringkat yang diharapkan. System.CommandLine menolak argumen yang tidak sesuai dengan ekspektasi ini.

Misalnya, kesalahan uraian ditampilkan jika argumen untuk opsi integer bukan bilangan bulat.

myapp --delay not-an-int
Cannot parse argument 'not-an-int' as System.Int32.

Kesalahan peringkat ditampilkan jika beberapa argumen diteruskan ke opsi yang memiliki peringkat maksimum satu:

myapp --delay-option 1 --delay-option 2
Option '--delay' expects a single argument but 2 were provided.

Perilaku ini dapat ditimpa dengan mengatur Option.AllowMultipleArgumentsPerToken ke true. Dalam hal ini Anda dapat mengulangi opsi yang memiliki peringkat maksimum satu, tetapi hanya nilai terakhir pada baris yang diterima. Dalam contoh berikut, nilai three akan diteruskan ke perintah.

myapp --item one --item two --item three

Parameter yang mengikat hingga 8 opsi dan argumen

Contoh berikut menunjukkan cara mengikat opsi ke parameter penanganan perintah, dengan memanggil SetHandler:

var delayOption = new Option<int>
    ("--delay", "An option whose argument is parsed as an int.");
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand("Parameter binding example");
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);

rootCommand.SetHandler(
    (delayOptionValue, messageOptionValue) =>
    {
        DisplayIntAndString(delayOptionValue, messageOptionValue);
    },
    delayOption, messageOption);

await rootCommand.InvokeAsync(args);
public static void DisplayIntAndString(int delayOptionValue, string messageOptionValue)
{
    Console.WriteLine($"--delay = {delayOptionValue}");
    Console.WriteLine($"--message = {messageOptionValue}");
}

Parameter lambda adalah variabel yang mewakili nilai opsi dan argumen:

(delayOptionValue, messageOptionValue) =>
{
    DisplayIntAndString(delayOptionValue, messageOptionValue);
},

Variabel yang mengikuti lambda mewakili objek opsi dan argumen yang merupakan sumber nilai opsi dan argumen:

delayOption, messageOption);

Opsi dan argumen harus dideklarasikan dalam urutan yang sama di lambda dan dalam parameter yang mengikuti lambda. Jika pesanan tidak konsisten, salah satu skenario berikut akan menghasilkan:

  • Jika opsi atau argumen di luar urutan memiliki jenis yang berbeda, pengecualian run-time akan dilepaskan. Misalnya, int mungkin muncul ketika harus string berada dalam daftar sumber.
  • Jika opsi atau argumen di luar urutan memiliki jenis yang sama, penanganan diam-diam mendapatkan nilai yang salah dalam parameter yang diberikan padanya. Misalnya, string opsi x mungkin muncul ketika string opsi y seharusnya ada dalam daftar sumber. Dalam hal ini, variabel untuk nilai opsi y mendapatkan nilai opsi x.

Ada overload SetHandler yang mendukung hingga 8 parameter, dengan tanda tangan sinkron dan asinkron.

Pengikatan parameter lebih dari 8 opsi dan argumen

Untuk menangani lebih dari 8 opsi, atau untuk membuat jenis kustom dari beberapa opsi, Anda dapat menggunakan InvocationContext atau pengikat kustom.

Menggunakan InvocationContext

Overload SetHandler menyediakan akses ke objek InvocationContext, dan Anda dapat menggunakan InvocationContext untuk mendapatkan sejumlah opsi dan nilai argumen. Misalnya, lihat Mengatur kode keluar dan Menangani penghentian.

Menggunakan pengikat kustom

Pengikat kustom memungkinkan Anda menggabungkan beberapa opsi atau nilai argumen ke dalam jenis yang kompleks dan meneruskannya ke dalam satu parameter penangan. Misalkan Anda memiliki Person jenis:

public class Person
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}

Buat kelas yang berasal dari BinderBase<T>, yang menandakan T jenis yang akan dibangun berdasarkan input baris perintah:

public class PersonBinder : BinderBase<Person>
{
    private readonly Option<string> _firstNameOption;
    private readonly Option<string> _lastNameOption;

    public PersonBinder(Option<string> firstNameOption, Option<string> lastNameOption)
    {
        _firstNameOption = firstNameOption;
        _lastNameOption = lastNameOption;
    }

    protected override Person GetBoundValue(BindingContext bindingContext) =>
        new Person
        {
            FirstName = bindingContext.ParseResult.GetValueForOption(_firstNameOption),
            LastName = bindingContext.ParseResult.GetValueForOption(_lastNameOption)
        };
}

Dengan pengikat kustom, Anda bisa mendapatkan jenis kustom Anda diteruskan ke penanganan Anda dengan cara yang sama Anda mendapatkan nilai untuk opsi dan argumen:

rootCommand.SetHandler((fileOptionValue, person) =>
    {
        DoRootCommand(fileOptionValue, person);
    },
    fileOption, new PersonBinder(firstNameOption, lastNameOption));

Berikut adalah program lengkap yang contoh-contoh sebelumnya diambil dari:

using System.CommandLine;
using System.CommandLine.Binding;

public class Program
{
    internal static async Task Main(string[] args)
    {
        var fileOption = new Option<FileInfo?>(
              name: "--file",
              description: "An option whose argument is parsed as a FileInfo",
              getDefaultValue: () => new FileInfo("scl.runtimeconfig.json"));

        var firstNameOption = new Option<string>(
              name: "--first-name",
              description: "Person.FirstName");

        var lastNameOption = new Option<string>(
              name: "--last-name",
              description: "Person.LastName");

        var rootCommand = new RootCommand();
        rootCommand.Add(fileOption);
        rootCommand.Add(firstNameOption);
        rootCommand.Add(lastNameOption);

        rootCommand.SetHandler((fileOptionValue, person) =>
            {
                DoRootCommand(fileOptionValue, person);
            },
            fileOption, new PersonBinder(firstNameOption, lastNameOption));

        await rootCommand.InvokeAsync(args);
    }

    public static void DoRootCommand(FileInfo? aFile, Person aPerson)
    {
        Console.WriteLine($"File = {aFile?.FullName}");
        Console.WriteLine($"Person = {aPerson?.FirstName} {aPerson?.LastName}");
    }

    public class Person
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }

    public class PersonBinder : BinderBase<Person>
    {
        private readonly Option<string> _firstNameOption;
        private readonly Option<string> _lastNameOption;

        public PersonBinder(Option<string> firstNameOption, Option<string> lastNameOption)
        {
            _firstNameOption = firstNameOption;
            _lastNameOption = lastNameOption;
        }

        protected override Person GetBoundValue(BindingContext bindingContext) =>
            new Person
            {
                FirstName = bindingContext.ParseResult.GetValueForOption(_firstNameOption),
                LastName = bindingContext.ParseResult.GetValueForOption(_lastNameOption)
            };
    }
}

Mengatur kode keluar

Ada Taskkelebihan beban Fungsi -pengembalian dari SetHandler. Jika pengatur Anda dipanggil dari kode asinkron, Anda dapat mengembalikan Task<int> dari penanganan yang menggunakan salah satu dari ini, dan menggunakan int nilai untuk mengatur kode keluar proses, seperti dalam contoh berikut:

static async Task<int> Main(string[] args)
{
    var delayOption = new Option<int>("--delay");
    var messageOption = new Option<string>("--message");

    var rootCommand = new RootCommand("Parameter binding example");
    rootCommand.Add(delayOption);
    rootCommand.Add(messageOption);

    rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
        {
            Console.WriteLine($"--delay = {delayOptionValue}");
            Console.WriteLine($"--message = {messageOptionValue}");
            return Task.FromResult(100);
        },
        delayOption, messageOption);

    return await rootCommand.InvokeAsync(args);
}

Namun, jika lambda itu sendiri perlu asinkron, Anda tidak dapat mengembalikan Task<int>. Dalam hal ini, gunakan InvocationContext.ExitCode. Anda bisa mendapatkan instans InvocationContext yang disuntikkan ke lambda Anda dengan menggunakan overload SetHandler yang menetapkan InvocationContext sebagai satu-satunya parameter. Overload SetHandler ini tidak memungkinkan Anda menentukan objek IValueDescriptor<T>, tetapi Anda bisa mendapatkan nilai opsi dan argumen dari properti ParseResult dari InvocationContext, seperti yang ditunjukkan dalam contoh berikut:

static async Task<int> Main(string[] args)
{
    var delayOption = new Option<int>("--delay");
    var messageOption = new Option<string>("--message");

    var rootCommand = new RootCommand("Parameter binding example");
    rootCommand.Add(delayOption);
    rootCommand.Add(messageOption);

    rootCommand.SetHandler(async (context) =>
        {
            int delayOptionValue = context.ParseResult.GetValueForOption(delayOption);
            string? messageOptionValue = context.ParseResult.GetValueForOption(messageOption);
        
            Console.WriteLine($"--delay = {delayOptionValue}");
            await Task.Delay(delayOptionValue);
            Console.WriteLine($"--message = {messageOptionValue}");
            context.ExitCode = 100;
        });

    return await rootCommand.InvokeAsync(args);
}

Jika Anda tidak memiliki pekerjaan asinkron yang harus dilakukan, Anda dapat menggunakan Action kelebihan beban. Dalam hal ini, atur kode keluar dengan menggunakan InvocationContext.ExitCode cara yang sama seperti yang Anda lakukan dengan lambda asinkron.

Kode keluar default ke 1. Jika Anda tidak mengaturnya secara eksplisit, nilainya diatur ke 0 saat penanganan Anda keluar secara normal. Jika pengecualian dilemparkan, itu menjaga nilai default.

Jenis yang didukung

Contoh berikut menunjukkan kode yang mengikat beberapa jenis yang umum digunakan.

Enum

Nilai jenis enum terikat menurut nama, dan pengikatannya tidak peka huruf besar/kecil:

var colorOption = new Option<ConsoleColor>("--color");

var rootCommand = new RootCommand("Enum binding example");
rootCommand.Add(colorOption);

rootCommand.SetHandler((colorOptionValue) =>
    { Console.WriteLine(colorOptionValue); },
    colorOption);

await rootCommand.InvokeAsync(args);

Berikut adalah contoh input baris perintah dan output yang dihasilkan dari contoh sebelumnya:

myapp --color red
myapp --color RED
Red
Red

Array dan daftar

Banyak jenis umum yang diterapkan IEnumerable didukung. Contohnya:

var itemsOption = new Option<IEnumerable<string>>("--items")
    { AllowMultipleArgumentsPerToken = true };

var command = new RootCommand("IEnumerable binding example");
command.Add(itemsOption);

command.SetHandler((items) =>
    {
        Console.WriteLine(items.GetType());

        foreach (string item in items)
        {
            Console.WriteLine(item);
        }
    },
    itemsOption);

await command.InvokeAsync(args);

Berikut adalah contoh input baris perintah dan output yang dihasilkan dari contoh sebelumnya:

--items one --items two --items three
System.Collections.Generic.List`1[System.String]
one
two
three

Karena AllowMultipleArgumentsPerToken diatur ke true, input berikut menghasilkan output yang sama:

--items one two three

Jenis sistem berkas

Aplikasi baris perintah yang bekerja dengan sistem file dapat menggunakan jenis FileSystemInfo, FileInfo, dan DirectoryInfo. Contoh berikut menunjukkan penggunaan FileSystemInfo:

var fileOrDirectoryOption = new Option<FileSystemInfo>("--file-or-directory");

var command = new RootCommand();
command.Add(fileOrDirectoryOption);

command.SetHandler((fileSystemInfo) =>
    {
        switch (fileSystemInfo)
        {
            case FileInfo file                    :
                Console.WriteLine($"File name: {file.FullName}");
                break;
            case DirectoryInfo directory:
                Console.WriteLine($"Directory name: {directory.FullName}");
                break;
            default:
                Console.WriteLine("Not a valid file or directory name.");
                break;
        }
    },
    fileOrDirectoryOption);

await command.InvokeAsync(args);

Dengan FileInfo dan DirectoryInfo kode pencocokan pola tidak diperlukan:

var fileOption = new Option<FileInfo>("--file");

var command = new RootCommand();
command.Add(fileOption);

command.SetHandler((file) =>
    {
        if (file is not null)
        {
            Console.WriteLine($"File name: {file?.FullName}");
        }
        else
        {
            Console.WriteLine("Not a valid file name.");
        }
    },
    fileOption);

await command.InvokeAsync(args);

Jenis yang didukung

Banyak jenis yang memiliki konstruktor yang mengambil parameter string tunggal dapat diikat dengan cara ini. Misalnya, kode yang akan berfungsi dengan FileInfo bekerja dengan sebagai Uri gantinya.

var endpointOption = new Option<Uri>("--endpoint");

var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine($"URL: {uri?.ToString()}");
    },
    endpointOption);

await command.InvokeAsync(args);

Selain jenis sistem file dan Uri, jenis berikut didukung:

  • bool
  • byte
  • DateTime
  • DateTimeOffset
  • decimal
  • double
  • float
  • Guid
  • int
  • long
  • sbyte
  • short
  • uint
  • ulong
  • ushort

Menggunakan objek System.CommandLine

Ada overload SetHandler yang memberi Anda akses ke objek InvocationContext. Objek tersebut kemudian dapat digunakan untuk mengakses objek System.CommandLine lainnya. Misalnya, Anda memiliki akses ke objek berikut:

InvocationContext

Misalnya, lihat Mengatur kode keluar dan Menangani penghentian.

CancellationToken

Untuk informasi tentang cara menggunakan CancellationToken, lihat Cara menangani penghentian.

IConsole

IConsole membuat pengujian serta banyak skenario ekstensibilitas lebih mudah daripada menggunakan System.Console. Ini tersedia di properti InvocationContext.Console.

ParseResult

Objek ParseResult tersedia di properti InvocationContext.ParseResult. Ini adalah struktur database tunggal yang mewakili hasil penguraian input baris perintah. Anda dapat menggunakannya untuk memeriksa keberadaan opsi atau argumen pada baris perintah atau untuk mendapatkan ParseResult.UnmatchedTokens properti. Properti ini berisi daftar token yang diurai tetapi tidak cocok dengan perintah, opsi, atau argumen yang dikonfigurasi.

Daftar token yang tak tertandingi berguna dalam perintah yang berperilaku seperti pembungkus. Perintah pembungkus mengambil sekumpulan token dan meneruskannya ke perintah atau aplikasi lain. Perintah sudo di Linux adalah contohnya. Dibutuhkan nama pengguna untuk meniru diikuti dengan perintah untuk dijalankan. Contohnya:

sudo -u admin apt update

Baris perintah ini akan menjalankan apt update perintah sebagai pengguna admin.

Untuk menerapkan perintah pembungkus seperti ini, atur properti TreatUnmatchedTokensAsErrors perintah ke false. Kemudian ParseResult.UnmatchedTokens properti akan berisi semua argumen yang secara eksplisit bukan milik perintah. Dalam contoh sebelumnya, ParseResult.UnmatchedTokens akan berisi apt token dan update. Penanganan perintah Anda kemudian dapat meneruskan ke UnmatchedTokens pemanggilan shell baru, misalnya.

Validasi dan pengikatan kustom

Untuk menyediakan kode validasi kustom, panggil AddValidator perintah, opsi, atau argumen Anda, seperti yang ditunjukkan dalam contoh berikut:

var delayOption = new Option<int>("--delay");
delayOption.AddValidator(result =>
{
    if (result.GetValueForOption(delayOption) < 1)
    {
        result.ErrorMessage = "Must be greater than 0";
    }
});

Jika Anda ingin mengurai serta memvalidasi input, gunakan delegasi, seperti yang ParseArgument<T> ditunjukkan dalam contoh berikut:

var delayOption = new Option<int>(
      name: "--delay",
      description: "An option whose argument is parsed as an int.",
      isDefault: true,
      parseArgument: result =>
      {
          if (!result.Tokens.Any())
          {
              return 42;
          }

          if (int.TryParse(result.Tokens.Single().Value, out var delay))
          {
              if (delay < 1)
              {
                  result.ErrorMessage = "Must be greater than 0";
              }
              return delay;
          }
          else
          {
              result.ErrorMessage = "Not an int.";
              return 0; // Ignored.
          }
      });

Kode sebelumnya diatur isDefault ke true sehingga parseArgument delegasi akan dipanggil meskipun pengguna tidak memasukkan nilai untuk opsi ini.

Berikut adalah beberapa contoh apa yang dapat Anda lakukan dengan ParseArgument<T> yang tidak dapat Anda lakukan dengan AddValidator:

  • Penguraian jenis kustom, seperti Person kelas dalam contoh berikut:

    public class Person
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }
    
    var personOption = new Option<Person?>(
          name: "--person",
          description: "An option whose argument is parsed as a Person",
          parseArgument: result =>
          {
              if (result.Tokens.Count != 2)
              {
                  result.ErrorMessage = "--person requires two arguments";
                  return null;
              }
              return new Person
              {
                  FirstName = result.Tokens.First().Value,
                  LastName = result.Tokens.Last().Value
              };
          })
    {
        Arity = ArgumentArity.OneOrMore,
        AllowMultipleArgumentsPerToken = true
    };
    
  • Penguraian jenis string input lainnya (misalnya, uraikan "1,2,3" ke dalam int[]).

  • Peringkat dinamis. Misalnya, Anda memiliki dua argumen yang ditentukan sebagai array string, dan Anda harus menangani urutan string dalam input baris perintah. Metode ini ArgumentResult.OnlyTake memungkinkan Anda membagi string input secara dinamis di antara argumen.

Lihat juga

System.CommandLine ringkasan