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 harusstring
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
opsix
mungkin muncul ketikastring
opsiy
seharusnya ada dalam daftar sumber. Dalam hal ini, variabel untuk nilai opsiy
mendapatkan nilai opsix
.
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.