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
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.0.100
dotnet-version: 3.1.100
- name: Pack
run: dotnet pack CliFx --configuration Release

View File

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

View File

@@ -4,17 +4,17 @@ using CliFx.Benchmarks.Commands;
namespace CliFx.Benchmarks
{
[CoreJob]
[SimpleJob]
[RankColumn]
public class Benchmark
{
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
[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")]
public Task<int> ExecuteWithSystemCommandLine() => new SystemCommandLineCommand().ExecuteAsync(Arguments);
public async ValueTask<int> ExecuteWithSystemCommandLine() => await new SystemCommandLineCommand().ExecuteAsync(Arguments);
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments);

View File

@@ -3,14 +3,14 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="clipr" Version="1.6.1" />
<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="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" />
</ItemGroup>

View File

@@ -16,6 +16,6 @@ namespace CliFx.Benchmarks.Commands
[CommandOption("bool", 'b')]
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>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -31,7 +31,7 @@ namespace CliFx.Demo.Commands
_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
if (Published == default)
@@ -48,7 +48,7 @@ namespace CliFx.Demo.Commands
console.Output.WriteLine("Book added.");
console.RenderBook(book);
return Task.CompletedTask;
return default;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("msg", 'm')]
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.")]
public string Separator { get; set; } = "";
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
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
public bool NotAnOption { get; set; }
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(Dividend / Divisor);
return Task.CompletedTask;
return default;
}
}
}

View File

@@ -13,6 +13,6 @@ namespace CliFx.Tests.TestCommands
[CommandOption("fruits")]
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')]
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")]
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")]
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")]
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')]
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]
public class HelloWorldDefaultCommand : ICommand
{
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
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.")]
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.")]
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.")]
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 Task ExecuteAsync(IConsole console) => Task.CompletedTask;
public ValueTask ExecuteAsync(IConsole console) => default;
}
}

View File

@@ -43,7 +43,7 @@ namespace CliFx
_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
var isDebugMode = _configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified();
@@ -162,7 +162,7 @@ namespace CliFx
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
var command = _commandFactory.CreateCommand(targetCommandSchema);
@@ -178,7 +178,7 @@ namespace CliFx
}
/// <inheritdoc />
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
public async ValueTask<int> RunAsync(IReadOnlyList<string> commandLineArguments)
{
try
{

View File

@@ -2,7 +2,7 @@
<Import Project="../CliFx.props" />
<PropertyGroup>
<TargetFrameworks>net45;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.1;netstandard2.0;net45</TargetFrameworks>
<Authors>$(Company)</Authors>
<Description>Declarative framework for CLI applications</Description>
<PackageTags>command line executable interface framework parser arguments net core</PackageTags>
@@ -18,15 +18,18 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</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>
<None Include="../favicon.png" Pack="True" PackagePath="" />
</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>
/// Runs application with specified command line arguments and returns an exit code.
/// </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"/>.
/// This method is called when the command is invoked by a user through command line interface.
/// </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#
public static class Program
{
public static Task<int> Main(string[] args) =>
new CliApplicationBuilder()
public static async Task<int> Main(string[] args) =>
await new CliApplicationBuilder()
.AddCommandsFromThisAssembly()
.Build()
.RunAsync(args);
@@ -85,17 +85,17 @@ public class LogCommand : ICommand
[CommandOption("base", 'b', Description = "Logarithm base.")]
public double Base { get; set; } = 10;
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
var result = Math.Log(Value, Base);
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.
@@ -171,7 +171,7 @@ public class DivideCommand : ICommand
[CommandOption("divisor", IsRequired = true)]
public double Divisor { get; set; }
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
if (Math.Abs(Divisor) < double.Epsilon)
{
@@ -182,7 +182,7 @@ public class DivideCommand : ICommand
var result = Dividend / Divisor;
console.Output.WriteLine(result);
return Task.CompletedTask;
return default;
}
}
```
@@ -225,7 +225,7 @@ Cancelled or terminated app returns non-zero exit code.
[Command("cancel")]
public class CancellableCommand : ICommand
{
public async Task ExecuteAsync(IConsole console)
public async ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine("Printed");
@@ -248,7 +248,7 @@ For example, here is how you would configure your application to use [`Microsoft
```c#
public static class Program
{
public static Task<int> Main(string[] args)
public static async Task<int> Main(string[] args)
{
var services = new ServiceCollection();
@@ -260,7 +260,7 @@ public static class Program
var serviceProvider = services.BuildServiceProvider();
return new CliApplicationBuilder()
return await new CliApplicationBuilder()
.AddCommandsFromThisAssembly()
.UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type))
.Build()
@@ -285,7 +285,7 @@ public class UserAddCommand : ICommand
[CommandOption("email", 'e')]
public string Email { get; set; }
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
var validationResult = new UserAddCommandValidator().Validate(this);
if (!validationResult.IsValid)
@@ -358,13 +358,13 @@ public class ConcatCommand : ICommand
[CommandOption("right")]
public string Right { get; set; } = "world";
public Task ExecuteAsync(IConsole console)
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.Write(Left);
console.Output.Write(' ');
console.Output.Write(Right);
return Task.CompletedTask;
return default;
}
}
```