Use ValueTask

This commit is contained in:
Alexey Golub
2019-12-16 22:12:17 +02:00
parent d2b0b16121
commit 6ce52c70f7
32 changed files with 71 additions and 68 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- name: Install .NET Core - name: Install .NET Core
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 3.0.100 dotnet-version: 3.1.100
- name: Pack - name: Pack
run: dotnet pack CliFx --configuration Release run: dotnet pack CliFx --configuration Release

View File

@@ -13,7 +13,7 @@ jobs:
- name: Install .NET Core - name: Install .NET Core
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 3.0.100 dotnet-version: 3.1.100
- name: Build & test - name: Build & test
run: dotnet test --configuration Release run: dotnet test --configuration Release

View File

@@ -4,17 +4,17 @@ using CliFx.Benchmarks.Commands;
namespace CliFx.Benchmarks namespace CliFx.Benchmarks
{ {
[CoreJob] [SimpleJob]
[RankColumn] [RankColumn]
public class Benchmark public class Benchmark
{ {
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"}; private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
[Benchmark(Description = "CliFx", Baseline = true)] [Benchmark(Description = "CliFx", Baseline = true)]
public Task<int> ExecuteWithCliFx() => new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments); public async ValueTask<int> ExecuteWithCliFx() => await new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments);
[Benchmark(Description = "System.CommandLine")] [Benchmark(Description = "System.CommandLine")]
public Task<int> ExecuteWithSystemCommandLine() => new SystemCommandLineCommand().ExecuteAsync(Arguments); public async ValueTask<int> ExecuteWithSystemCommandLine() => await new SystemCommandLineCommand().ExecuteAsync(Arguments);
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")] [Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments); public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments);

View File

@@ -3,14 +3,14 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" /> <PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="clipr" Version="1.6.1" /> <PackageReference Include="clipr" Version="1.6.1" />
<PackageReference Include="CommandLineParser" Version="2.6.0" /> <PackageReference Include="CommandLineParser" Version="2.6.0" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.4.4" />
<PackageReference Include="PowerArgs" Version="3.6.0" /> <PackageReference Include="PowerArgs" Version="3.6.0" />
<PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" /> <PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" />
</ItemGroup> </ItemGroup>

View File

@@ -16,6 +16,6 @@ namespace CliFx.Benchmarks.Commands
[CommandOption("bool", 'b')] [CommandOption("bool", 'b')]
public bool BoolOption { get; set; } public bool BoolOption { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -3,12 +3,12 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -31,7 +31,7 @@ namespace CliFx.Demo.Commands
_libraryService = libraryService; _libraryService = libraryService;
} }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
// To make the demo simpler, we will just generate random publish date and ISBN if they were not set // To make the demo simpler, we will just generate random publish date and ISBN if they were not set
if (Published == default) if (Published == default)
@@ -48,7 +48,7 @@ namespace CliFx.Demo.Commands
console.Output.WriteLine("Book added."); console.Output.WriteLine("Book added.");
console.RenderBook(book); console.RenderBook(book);
return Task.CompletedTask; return default;
} }
} }

View File

@@ -20,7 +20,7 @@ namespace CliFx.Demo.Commands
_libraryService = libraryService; _libraryService = libraryService;
} }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
var book = _libraryService.GetBook(Title); var book = _libraryService.GetBook(Title);
@@ -29,7 +29,7 @@ namespace CliFx.Demo.Commands
console.RenderBook(book); console.RenderBook(book);
return Task.CompletedTask; return default;
} }
} }
} }

View File

@@ -16,7 +16,7 @@ namespace CliFx.Demo.Commands
_libraryService = libraryService; _libraryService = libraryService;
} }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
var library = _libraryService.GetLibrary(); var library = _libraryService.GetLibrary();
@@ -32,7 +32,7 @@ namespace CliFx.Demo.Commands
console.RenderBook(book); console.RenderBook(book);
} }
return Task.CompletedTask; return default;
} }
} }
} }

View File

@@ -19,7 +19,7 @@ namespace CliFx.Demo.Commands
_libraryService = libraryService; _libraryService = libraryService;
} }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
var book = _libraryService.GetBook(Title); var book = _libraryService.GetBook(Title);
@@ -30,7 +30,7 @@ namespace CliFx.Demo.Commands
console.Output.WriteLine($"Book {Title} removed."); console.Output.WriteLine($"Book {Title} removed.");
return Task.CompletedTask; return default;
} }
} }
} }

View File

@@ -25,11 +25,11 @@ namespace CliFx.Demo
return services.BuildServiceProvider(); return services.BuildServiceProvider();
} }
public static Task<int> Main(string[] args) public static async Task<int> Main(string[] args)
{ {
var serviceProvider = ConfigureServices(); var serviceProvider = ConfigureServices();
return new CliApplicationBuilder() return await new CliApplicationBuilder()
.AddCommandsFromThisAssembly() .AddCommandsFromThisAssembly()
.UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type)) .UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type))
.Build() .Build()

View File

@@ -2,7 +2,7 @@
<Import Project="../CliFx.props" /> <Import Project="../CliFx.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<CollectCoverage>true</CollectCoverage> <CollectCoverage>true</CollectCoverage>

View File

@@ -8,7 +8,7 @@ namespace CliFx.Tests.TestCommands
[Command("cancel")] [Command("cancel")]
public class CancellableCommand : ICommand public class CancellableCommand : ICommand
{ {
public async Task ExecuteAsync(IConsole console) public async ValueTask ExecuteAsync(IConsole console)
{ {
await Task.Yield(); await Task.Yield();

View File

@@ -14,6 +14,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("msg", 'm')] [CommandOption("msg", 'm')]
public string? Message { get; set; } public string? Message { get; set; }
public Task ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode); public ValueTask ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode);
} }
} }

View File

@@ -14,10 +14,10 @@ namespace CliFx.Tests.TestCommands
[CommandOption('s', Description = "String separator.")] [CommandOption('s', Description = "String separator.")]
public string Separator { get; set; } = ""; public string Separator { get; set; } = "";
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
console.Output.WriteLine(string.Join(Separator, Inputs)); console.Output.WriteLine(string.Join(Separator, Inputs));
return Task.CompletedTask; return default;
} }
} }
} }

View File

@@ -16,10 +16,10 @@ namespace CliFx.Tests.TestCommands
// This property should be ignored by resolver // This property should be ignored by resolver
public bool NotAnOption { get; set; } public bool NotAnOption { get; set; }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
console.Output.WriteLine(Dividend / Divisor); console.Output.WriteLine(Dividend / Divisor);
return Task.CompletedTask; return default;
} }
} }
} }

View File

@@ -13,6 +13,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("fruits")] [CommandOption("fruits")]
public string? Oranges { get; set; } public string? Oranges { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -13,6 +13,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption('f')] [CommandOption('f')]
public string? Oranges { get; set; } public string? Oranges { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -10,6 +10,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("opt", EnvironmentVariableName = "ENV_SINGLE_VALUE")] [CommandOption("opt", EnvironmentVariableName = "ENV_SINGLE_VALUE")]
public string? Option { get; set; } public string? Option { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -11,6 +11,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")] [CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
public IEnumerable<string>? Option { get; set; } public IEnumerable<string>? Option { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -10,6 +10,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")] [CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
public string? Option { get; set; } public string? Option { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -11,6 +11,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("msg", 'm')] [CommandOption("msg", 'm')]
public string? Message { get; set; } public string? Message { get; set; }
public Task ExecuteAsync(IConsole console) => throw new Exception(Message); public ValueTask ExecuteAsync(IConsole console) => throw new Exception(Message);
} }
} }

View File

@@ -7,10 +7,10 @@ namespace CliFx.Tests.TestCommands
[Command] [Command]
public class HelloWorldDefaultCommand : ICommand public class HelloWorldDefaultCommand : ICommand
{ {
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
console.Output.WriteLine("Hello world."); console.Output.WriteLine("Hello world.");
return Task.CompletedTask; return default;
} }
} }
} }

View File

@@ -13,6 +13,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("option-b", 'b', Description = "OptionB description.")] [CommandOption("option-b", 'b', Description = "OptionB description.")]
public string? OptionB { get; set; } public string? OptionB { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -13,6 +13,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("option-d", 'd', Description = "OptionD description.")] [CommandOption("option-d", 'd', Description = "OptionD description.")]
public string? OptionD { get; set; } public string? OptionD { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -10,6 +10,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("option-e", 'e', Description = "OptionE description.")] [CommandOption("option-e", 'e', Description = "OptionE description.")]
public string? OptionE { get; set; } public string? OptionE { get; set; }
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -5,6 +5,6 @@ namespace CliFx.Tests.TestCommands
{ {
public class NonAnnotatedCommand : ICommand public class NonAnnotatedCommand : ICommand
{ {
public Task ExecuteAsync(IConsole console) => Task.CompletedTask; public ValueTask ExecuteAsync(IConsole console) => default;
} }
} }

View File

@@ -43,7 +43,7 @@ namespace CliFx
_helpTextRenderer = helpTextRenderer; _helpTextRenderer = helpTextRenderer;
} }
private async Task<int?> HandleDebugDirectiveAsync(CommandInput commandInput) private async ValueTask<int?> HandleDebugDirectiveAsync(CommandInput commandInput)
{ {
// Debug mode is enabled if it's allowed in the application and it was requested via corresponding directive // Debug mode is enabled if it's allowed in the application and it was requested via corresponding directive
var isDebugMode = _configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified(); var isDebugMode = _configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified();
@@ -162,7 +162,7 @@ namespace CliFx
return isError ? -1 : 0; return isError ? -1 : 0;
} }
private async Task<int> HandleCommandExecutionAsync(CommandInput commandInput, CommandSchema targetCommandSchema) private async ValueTask<int> HandleCommandExecutionAsync(CommandInput commandInput, CommandSchema targetCommandSchema)
{ {
// Create an instance of the command // Create an instance of the command
var command = _commandFactory.CreateCommand(targetCommandSchema); var command = _commandFactory.CreateCommand(targetCommandSchema);
@@ -178,7 +178,7 @@ namespace CliFx
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments) public async ValueTask<int> RunAsync(IReadOnlyList<string> commandLineArguments)
{ {
try try
{ {

View File

@@ -2,7 +2,7 @@
<Import Project="../CliFx.props" /> <Import Project="../CliFx.props" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net45;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>netstandard2.1;netstandard2.0;net45</TargetFrameworks>
<Authors>$(Company)</Authors> <Authors>$(Company)</Authors>
<Description>Declarative framework for CLI applications</Description> <Description>Declarative framework for CLI applications</Description>
<PackageTags>command line executable interface framework parser arguments net core</PackageTags> <PackageTags>command line executable interface framework parser arguments net core</PackageTags>
@@ -18,15 +18,18 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="all" />
<PackageReference Include="Nullable" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="../favicon.png" Pack="True" PackagePath="" /> <None Include="../favicon.png" Pack="True" PackagePath="" />
</ItemGroup> </ItemGroup>
</Project> <ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="Nullable" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net45'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
</ItemGroup>
</Project>

View File

@@ -11,6 +11,6 @@ namespace CliFx
/// <summary> /// <summary>
/// Runs application with specified command line arguments and returns an exit code. /// Runs application with specified command line arguments and returns an exit code.
/// </summary> /// </summary>
Task<int> RunAsync(IReadOnlyList<string> commandLineArguments); ValueTask<int> RunAsync(IReadOnlyList<string> commandLineArguments);
} }
} }

View File

@@ -12,6 +12,6 @@ namespace CliFx
/// Executes command using specified implementation of <see cref="IConsole"/>. /// Executes command using specified implementation of <see cref="IConsole"/>.
/// This method is called when the command is invoked by a user through command line interface. /// This method is called when the command is invoked by a user through command line interface.
/// </summary> /// </summary>
Task ExecuteAsync(IConsole console); ValueTask ExecuteAsync(IConsole console);
} }
} }

View File

@@ -59,8 +59,8 @@ The following code will create and run default `CliApplication` that will resolv
```c# ```c#
public static class Program public static class Program
{ {
public static Task<int> Main(string[] args) => public static async Task<int> Main(string[] args) =>
new CliApplicationBuilder() await new CliApplicationBuilder()
.AddCommandsFromThisAssembly() .AddCommandsFromThisAssembly()
.Build() .Build()
.RunAsync(args); .RunAsync(args);
@@ -85,17 +85,17 @@ public class LogCommand : ICommand
[CommandOption("base", 'b', Description = "Logarithm base.")] [CommandOption("base", 'b', Description = "Logarithm base.")]
public double Base { get; set; } = 10; public double Base { get; set; } = 10;
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
var result = Math.Log(Value, Base); var result = Math.Log(Value, Base);
console.Output.WriteLine(result); console.Output.WriteLine(result);
return Task.CompletedTask; return default;
} }
} }
``` ```
By implementing `ICommand` this class also provides `ExecuteAsync` method. This is the method that gets called when the user invokes the command. Its return type is `Task` in order to facilitate asynchronous execution, but if your command runs synchronously you can simply return `Task.CompletedTask`. By implementing `ICommand` this class also provides `ExecuteAsync` method. This is the method that gets called when the user invokes the command. Its return type is `ValueTask` in order to facilitate both synchronous and asynchronous execution. If your command always runs synchronously, simply return `default` at the end of the method.
The `ExecuteAsync` method also takes an instance of `IConsole` as a parameter. You should use the `console` parameter in places where you would normally use `System.Console`, in order to make your command testable. The `ExecuteAsync` method also takes an instance of `IConsole` as a parameter. You should use the `console` parameter in places where you would normally use `System.Console`, in order to make your command testable.
@@ -171,7 +171,7 @@ public class DivideCommand : ICommand
[CommandOption("divisor", IsRequired = true)] [CommandOption("divisor", IsRequired = true)]
public double Divisor { get; set; } public double Divisor { get; set; }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
if (Math.Abs(Divisor) < double.Epsilon) if (Math.Abs(Divisor) < double.Epsilon)
{ {
@@ -182,7 +182,7 @@ public class DivideCommand : ICommand
var result = Dividend / Divisor; var result = Dividend / Divisor;
console.Output.WriteLine(result); console.Output.WriteLine(result);
return Task.CompletedTask; return default;
} }
} }
``` ```
@@ -225,7 +225,7 @@ Cancelled or terminated app returns non-zero exit code.
[Command("cancel")] [Command("cancel")]
public class CancellableCommand : ICommand public class CancellableCommand : ICommand
{ {
public async Task ExecuteAsync(IConsole console) public async ValueTask ExecuteAsync(IConsole console)
{ {
console.Output.WriteLine("Printed"); console.Output.WriteLine("Printed");
@@ -248,7 +248,7 @@ For example, here is how you would configure your application to use [`Microsoft
```c# ```c#
public static class Program public static class Program
{ {
public static Task<int> Main(string[] args) public static async Task<int> Main(string[] args)
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
@@ -260,7 +260,7 @@ public static class Program
var serviceProvider = services.BuildServiceProvider(); var serviceProvider = services.BuildServiceProvider();
return new CliApplicationBuilder() return await new CliApplicationBuilder()
.AddCommandsFromThisAssembly() .AddCommandsFromThisAssembly()
.UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type)) .UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type))
.Build() .Build()
@@ -285,7 +285,7 @@ public class UserAddCommand : ICommand
[CommandOption("email", 'e')] [CommandOption("email", 'e')]
public string Email { get; set; } public string Email { get; set; }
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
var validationResult = new UserAddCommandValidator().Validate(this); var validationResult = new UserAddCommandValidator().Validate(this);
if (!validationResult.IsValid) if (!validationResult.IsValid)
@@ -358,13 +358,13 @@ public class ConcatCommand : ICommand
[CommandOption("right")] [CommandOption("right")]
public string Right { get; set; } = "world"; public string Right { get; set; } = "world";
public Task ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
console.Output.Write(Left); console.Output.Write(Left);
console.Output.Write(' '); console.Output.Write(' ');
console.Output.Write(Right); console.Output.Write(Right);
return Task.CompletedTask; return default;
} }
} }
``` ```