diff --git a/.editorconfig b/.editorconfig index 172eda6..758e8ca 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,6 +23,9 @@ indent_style = tab # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true +# Omit accessibility modifiers when they match the language default (e.g. private on class members) +dotnet_style_require_accessibility_modifiers = omit_if_default:error + # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:error dotnet_style_qualification_for_property = false:error @@ -42,6 +45,12 @@ dotnet_style_explicit_tuple_names = true:error # CSharp code style settings: [*.cs] +# Prefer file-scoped namespace declarations (single `namespace Foo;`) +csharp_style_namespace_declarations = file_scoped:error + +# Allow single-statement bodies without braces (e.g. `if (x) F();`, `for (...) F();`) +csharp_prefer_braces = false:silent + # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:error csharp_style_var_when_type_is_apparent = true:error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 840862c..fe6264a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: 8.0.* 9.0.* 10.0.* + 11.0.* env: DOTNET_NOLOGO: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 @@ -25,3 +26,7 @@ jobs: run: dotnet build --no-restore - name: Test run: dotnet test --no-build -v n /p:CollectCoverage=true /p:CoverletOutput='../../' /p:CoverletOutputFormat=opencover /p:Threshold=91 /p:SkipAutoProps=true /p:UseSourceLink=true + - name: AOT smoke-test (net8.0) + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net8.0 --no-restore + - name: AOT smoke-test (net11.0) + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net11.0 --no-restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad06177..ccdfa2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.0.* + dotnet-version: 11.0.* env: DOTNET_NOLOGO: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 diff --git a/CLAUDE.md b/CLAUDE.md index 82078d1..3a1b6b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,26 +2,30 @@ ## Project Overview -TaskTupleAwaiter provides extension methods that allow you to `await` a `ValueTuple` of `Task` (or non-generic `Task`) instances and destructure the results in a single line. The `source_generator` branch replaces the hand-authored `TaskTupleExtensions.cs` (up to arity 16) with a Roslyn incremental source generator. +TaskTupleAwaiter provides extension methods that allow you to `await` a `ValueTuple` of `Task` (or non-generic `Task`) instances and destructure the results in a single line. In this repository, a Roslyn incremental source generator (`src/TaskTupleAwaiter.Generator`) generates the extension-method source during library build under `namespace System.Threading.Tasks`, and that generated code is compiled into `TaskTupleAwaiter.dll` for each target framework. Consumers install and reference the compiled package binaries; the generator is a private build-time implementation detail. ## Repository Layout ``` TaskTupleAwaiter/ ├── src/ -│ ├── TaskTupleAwaiter/ # Main library (netstandard2.0) -│ │ └── TaskTupleExtensions.cs # Hand-authored fallback / reference implementation +│ ├── TaskTupleAwaiter/ # Main library shell (netstandard2.0, net462, net8.0) +│ │ # No hand-authored .cs sources — code is generated at build and compiled into the library. │ └── TaskTupleAwaiter.Generator/ # Roslyn incremental source generator (netstandard2.0) │ └── TaskTupleExtensionsGenerator.cs ├── test/ -│ └── TaskTupleAwaiter.Tests/ # xUnit test project -│ ├── TaskTupleAwaiterTests.cs -│ ├── BehaviorComparisonTests.cs -│ ├── Adapters/ -│ │ └── AwaiterAdapter.cs -│ ├── DummyException.cs -│ ├── On.cs -│ └── SpySynchronizationContext.cs +│ ├── TaskTupleAwaiter.Tests/ # xUnit v3 test project +│ │ ├── TaskTupleAwaiterTests.cs +│ │ ├── BehaviorComparisonTests.cs +│ │ ├── Adapters/ +│ │ │ └── AwaiterAdapter.cs +│ │ ├── DummyException.cs +│ │ ├── On.cs +│ │ └── SpySynchronizationContext.cs +│ └── TaskTupleAwaiter.AotSmokeTest/ # NativeAOT downstream-consumer smoke-test (net8.0, net11.0) +│ ├── TaskTupleAwaiter.AotSmokeTest.csproj +│ └── Program.cs +├── docs/superpowers/ # Specs and implementation plans ├── README.md ├── LICENSE.txt └── CLAUDE.md # This file @@ -32,7 +36,9 @@ TaskTupleAwaiter/ | Concern | Choice | |---|---| | Language | C# 14.0 | -| Library / Generator target | .NET Standard 2.0 | +| Library TFMs | netstandard2.0, net462, net8.0 | +| Generator target | netstandard2.0 (Roslyn analyzer requirement) | +| AOT-compatible TFMs | net8.0+ (`true` via `IsTargetFrameworkCompatible`) | | Generator framework | Roslyn `IIncrementalGenerator` | | Test framework | xUnit v3 | | Assertion library | Shouldly | diff --git a/Directory.Build.props b/Directory.Build.props index 0c5d46e..75ad24b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,10 @@ + latest-Recommended + true + true true latest - true - latest-Recommended true diff --git a/README.md b/README.md index 8fdb3bd..20f7296 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ var (user, orders) = await (GetUserAsync(id), GetOrdersAsync(id)); - **Non-generic `Task` support** — await tuples of `Task` (not just `Task`) when you don't need return values - **Zero dependencies** — a single file, no external packages (except `System.ValueTuple` on .NET Framework 4.6.2) - **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, and .NET 8+ +- **NativeAOT ready** — the package sets `true` for .NET 8+ targets, and CI publishes downstream NativeAOT smoke tests ## Installation diff --git a/TaskTupleAwaiter.slnx b/TaskTupleAwaiter.slnx index b741ce6..62f649d 100644 --- a/TaskTupleAwaiter.slnx +++ b/TaskTupleAwaiter.slnx @@ -23,4 +23,5 @@ + \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index da49490..9f76723 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,30 +1,29 @@ - + - - netstandard2.0;net462;net8.0 - Joseph Musser,Brian Buvinghausen - Buvinghausen Solutions - Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen - MIT - README.md - https://github.com/buvinghausen/TaskTupleAwaiter/blob/master/README.md - C# 7.0;Value Tuple;Async Await Elegant Code - true - true - true - true - snupkg - true - ..\..\ - + + Joseph Musser,Brian Buvinghausen + Buvinghausen Solutions + true + Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen + true + true + true + MIT + ..\..\ + https://github.com/buvinghausen/TaskTupleAwaiter/blob/master/README.md + README.md + C# 7.0;Value Tuple;Async Await Elegant Code + true + snupkg + netstandard2.0;net462;net8.0 + - - - - - - + + + + + diff --git a/src/TaskTupleAwaiter.Generator/CSharpEmit.cs b/src/TaskTupleAwaiter.Generator/CSharpEmit.cs new file mode 100644 index 0000000..3203f3b --- /dev/null +++ b/src/TaskTupleAwaiter.Generator/CSharpEmit.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace TaskTupleAwaiter.Generator; + +static class CSharpEmit +{ + // Identical to StringBuilder.AppendLine at runtime; the [StringSyntax("C#")] annotation + // is what Visual Studio 2022 and JetBrains Rider use to syntax-highlight the raw-string + // content at each call site as C# instead of as opaque text. + public static StringBuilder AppendCSharp(this StringBuilder sb, [StringSyntax("C#")] string code) => + sb.AppendLine(code); +} diff --git a/src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs b/src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs new file mode 100644 index 0000000..78a3ef4 --- /dev/null +++ b/src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs @@ -0,0 +1,13 @@ +// Polyfill for System.Diagnostics.CodeAnalysis.StringSyntaxAttribute (added in .NET 7). +// The generator project targets netstandard2.0, so the BCL definition isn't available. +// Roslyn's IDE classifiers recognise the attribute by namespace + type name (not assembly +// identity), so an internal declaration here drives the embedded-language hint in VS / Rider. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)] +sealed class StringSyntaxAttribute(string syntax) : Attribute +{ + public string Syntax { get; } = syntax; +} diff --git a/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj b/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj index 1a0f05b..0b17e95 100644 --- a/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj +++ b/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj @@ -1,15 +1,15 @@ - + - - netstandard2.0 - true - true - false - false - + + true + false + true + $(NoWarn);CS1591 + netstandard2.0 + - - - + + + diff --git a/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs b/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs index 4e287d7..05a8b6d 100644 --- a/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs +++ b/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs @@ -28,7 +28,7 @@ static string GenerateSource(bool hasAwaitOptions) { StringBuilder sb = new(); - sb.AppendLine( + sb.AppendCSharp( """ // using System.Runtime.CompilerServices; @@ -55,7 +55,7 @@ public static class TaskTupleExtensions AppendTypedArity(sb, arity, hasAwaitOptions); AppendNonGenericSection(sb, hasAwaitOptions); - sb.AppendLine("}"); + sb.AppendCSharp("}"); return sb.ToString(); } @@ -63,7 +63,7 @@ public static class TaskTupleExtensions static void AppendTypedArity1(StringBuilder sb, bool hasAwaitOptions) { - sb.AppendLine( + sb.AppendCSharp( """ #region (Task) @@ -78,7 +78,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTupleThis type and its members are intended for use by the compiler. public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple> tasks, ConfigureAwaitOptions options) => @@ -86,7 +86,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple..Task) @@ -117,7 +117,7 @@ static void AppendTypedArity(StringBuilder sb, int arity, bool hasAwaitOptions) """); if (hasAwaitOptions) - sb.AppendLine( + sb.AppendCSharp( $""" /// This type and its members are intended for use by the compiler. public static TupleConfiguredTaskAwaitable<{tp}> ConfigureAwait<{tp}>(this {tupleType} tasks, ConfigureAwaitOptions options) => @@ -129,14 +129,14 @@ static void AppendTypedArity(StringBuilder sb, int arity, bool hasAwaitOptions) AppendTupleConfiguredTaskAwaitableStruct(sb, arity, tp, tupleType, optionsType); - sb.AppendLine( + sb.AppendCSharp( """ #endregion """); } - static void AppendTupleTaskAwaiterStruct(StringBuilder sb, int arity, string tp, string tupleType) => sb.AppendLine( + static void AppendTupleTaskAwaiterStruct(StringBuilder sb, int arity, string tp, string tupleType) => sb.AppendCSharp( $$""" /// This type and its members are intended for use by the compiler. public readonly record struct TupleTaskAwaiter<{{tp}}> : ICriticalNotifyCompletion @@ -174,7 +174,7 @@ public void UnsafeOnCompleted(Action continuation) => """); static void AppendTupleConfiguredTaskAwaitableStruct(StringBuilder sb, int arity, string tp, - string tupleType, string optionsType) => sb.AppendLine( + string tupleType, string optionsType) => sb.AppendCSharp( $$""" /// This type and its members are intended for use by the compiler. public readonly record struct TupleConfiguredTaskAwaitable<{{tp}}> @@ -232,7 +232,7 @@ public void UnsafeOnCompleted(Action continuation) => static void AppendNonGenericSection(StringBuilder sb, bool hasAwaitOptions) { - sb.AppendLine( + sb.AppendCSharp( """ #region Task @@ -247,7 +247,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple tasks """); if (hasAwaitOptions) - sb.AppendLine( + sb.AppendCSharp( """ /// This type and its members are intended for use by the compiler. public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple tasks, ConfigureAwaitOptions options) => @@ -260,7 +260,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple tasks AppendNonGenericArity(sb, arity, hasAwaitOptions); } - sb.AppendLine( + sb.AppendCSharp( """ #endregion @@ -271,7 +271,7 @@ static void AppendNonGenericArity(StringBuilder sb, int arity, bool hasAwaitOpti { var tupleType = NonGenericTaskTupleType(arity); - sb.AppendLine( + sb.AppendCSharp( $""" /// This type and its members are intended for use by the compiler. public static TaskAwaiter GetAwaiter(this {tupleType} tasks) => @@ -285,7 +285,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this {tupleType} tasks, boo if (!hasAwaitOptions) return; - sb.AppendLine( + sb.AppendCSharp( $""" /// This type and its members are intended for use by the compiler. public static ConfiguredTaskAwaitable ConfigureAwait(this {tupleType} tasks, ConfigureAwaitOptions options) => diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 7cf4525..0297231 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,19 +3,21 @@ - - net10.0;net9.0;net8.0;net472 - Exe false + $(NoWarn);CS1591;IDE0051 + Exe + net11.0;net10.0;net9.0;net8.0;net472 true true - - + + + + diff --git a/test/TaskTupleAwaiter.AotSmokeTest/Program.cs b/test/TaskTupleAwaiter.AotSmokeTest/Program.cs new file mode 100644 index 0000000..0f2c408 --- /dev/null +++ b/test/TaskTupleAwaiter.AotSmokeTest/Program.cs @@ -0,0 +1,44 @@ +// --- typed arity-1: ValueTuple> --- +// The arity-1 GetAwaiter delegates directly to the inner task's TaskAwaiter. +{ + var tuple = ValueTuple.Create(Task.FromResult(1)); + var a = await tuple; + Console.WriteLine($"arity-1 typed: {a}"); +} + +// --- typed arity-2 with ConfigureAwait(bool) --- +{ + var (b1, b2) = await (Task.FromResult(2), Task.FromResult("two")).ConfigureAwait(false); + Console.WriteLine($"arity-2 typed CA(false): {b1}, {b2}"); +} + +// --- typed arity-2 with ConfigureAwaitOptions --- +{ + var (c1, c2) = await (Task.FromResult(3), Task.FromResult("three")) + .ConfigureAwait(ConfigureAwaitOptions.None); + Console.WriteLine($"arity-2 typed CA(options): {c1}, {c2}"); +} + +// --- typed arity-16 (upper boundary of the generator's struct emission loop) --- +{ + var (d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16) = await ( + Task.FromResult(1), Task.FromResult(2), Task.FromResult(3), Task.FromResult(4), + Task.FromResult(5), Task.FromResult(6), Task.FromResult(7), Task.FromResult(8), + Task.FromResult(9), Task.FromResult(10), Task.FromResult(11), Task.FromResult(12), + Task.FromResult(13), Task.FromResult(14), Task.FromResult(15), Task.FromResult(16)); + Console.WriteLine($"arity-16 typed: {d1}..{d16} (sum check: {d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9 + d10 + d11 + d12 + d13 + d14 + d15 + d16})"); +} + +// --- non-generic Task tuple arity-2 --- +{ + await (Task.CompletedTask, Task.CompletedTask); + Console.WriteLine("non-generic arity-2: ok"); +} + +// --- non-generic Task tuple arity-2 with ConfigureAwaitOptions --- +{ + await (Task.CompletedTask, Task.CompletedTask).ConfigureAwait(ConfigureAwaitOptions.None); + Console.WriteLine("non-generic arity-2 CA(options): ok"); +} + +Console.WriteLine("AOT smoke-test completed."); diff --git a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj new file mode 100644 index 0000000..6d604de --- /dev/null +++ b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj @@ -0,0 +1,28 @@ + + + + false + Exe + true + true + net8.0;net11.0 + false + false + + + + + + + + + + + + diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.AwaiterAdapterAwaiter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.AwaiterAdapterAwaiter.cs index 0f47da2..d52568e 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.AwaiterAdapterAwaiter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.AwaiterAdapterAwaiter.cs @@ -6,7 +6,7 @@ partial class AwaiterAdapter { public readonly record struct AwaiterAdapterAwaiter : ICriticalNotifyCompletion { - private readonly AwaiterAdapter _awaiterAdapter; + readonly AwaiterAdapter _awaiterAdapter; public AwaiterAdapterAwaiter(AwaiterAdapter awaiterAdapter) => _awaiterAdapter = awaiterAdapter; diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskAwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskAwaiterAdapter.cs index 79237e7..b9f3c03 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskAwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskAwaiterAdapter.cs @@ -4,11 +4,11 @@ namespace TaskTupleAwaiter.Tests.Adapters; partial class AwaiterAdapter { - private sealed class ConfiguredTaskAwaiterAdapter( + sealed class ConfiguredTaskAwaiterAdapter( ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { - private ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter = awaiter; + readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter = awaiter; public override bool IsCompleted => _awaiter.IsCompleted; diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskTupleAwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskTupleAwaiterAdapter.cs index 9ecaf65..942b88c 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskTupleAwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.ConfiguredTaskTupleAwaiterAdapter.cs @@ -4,11 +4,11 @@ namespace TaskTupleAwaiter.Tests.Adapters; partial class AwaiterAdapter { - private sealed class ConfiguredTaskTupleAwaiter1Adapter( + sealed class ConfiguredTaskTupleAwaiter1Adapter( ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { - private ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter = awaiter; + readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter = awaiter; public override bool IsCompleted => _awaiter.IsCompleted; @@ -23,7 +23,7 @@ public override object[] GetResult() => [_awaiter.GetResult()]; } - private sealed class ConfiguredTaskTupleAwaiter2Adapter( + sealed class ConfiguredTaskTupleAwaiter2Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) { @@ -43,7 +43,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter3Adapter( + sealed class ConfiguredTaskTupleAwaiter3Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) { @@ -63,7 +63,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter4Adapter( + sealed class ConfiguredTaskTupleAwaiter4Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) { @@ -83,7 +83,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter5Adapter( + sealed class ConfiguredTaskTupleAwaiter5Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) { @@ -103,7 +103,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter6Adapter( + sealed class ConfiguredTaskTupleAwaiter6Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -124,7 +124,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter7Adapter( + sealed class ConfiguredTaskTupleAwaiter7Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -145,7 +145,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter8Adapter( + sealed class ConfiguredTaskTupleAwaiter8Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable .Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -166,7 +166,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter9Adapter( + sealed class ConfiguredTaskTupleAwaiter9Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -187,7 +187,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter10Adapter( + sealed class ConfiguredTaskTupleAwaiter10Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -208,7 +208,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter11Adapter( + sealed class ConfiguredTaskTupleAwaiter11Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -229,7 +229,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter12Adapter( + sealed class ConfiguredTaskTupleAwaiter12Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -251,7 +251,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter13Adapter( + sealed class ConfiguredTaskTupleAwaiter13Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -273,7 +273,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter14Adapter( + sealed class ConfiguredTaskTupleAwaiter14Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -298,7 +298,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter15Adapter( + sealed class ConfiguredTaskTupleAwaiter15Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) @@ -324,7 +324,7 @@ public override object[] GetResult() } } - private sealed class ConfiguredTaskTupleAwaiter16Adapter( + sealed class ConfiguredTaskTupleAwaiter16Adapter( TaskTupleExtensions.TupleConfiguredTaskAwaitable.Awaiter awaiter, string description) : AwaiterAdapter(description) diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskAwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskAwaiterAdapter.cs index 2791e9d..195fe2e 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskAwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskAwaiterAdapter.cs @@ -4,10 +4,10 @@ namespace TaskTupleAwaiter.Tests.Adapters; partial class AwaiterAdapter { - private sealed class TaskAwaiterAdapter(TaskAwaiter awaiter, string description) + sealed class TaskAwaiterAdapter(TaskAwaiter awaiter, string description) : AwaiterAdapter(description) { - private TaskAwaiter _awaiter = awaiter; + readonly TaskAwaiter _awaiter = awaiter; public override bool IsCompleted => _awaiter.IsCompleted; diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskTupleAwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskTupleAwaiterAdapter.cs index b35d4f3..02e44f2 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskTupleAwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.TaskTupleAwaiterAdapter.cs @@ -4,10 +4,10 @@ namespace TaskTupleAwaiter.Tests.Adapters; partial class AwaiterAdapter { - private sealed class TaskTupleAwaiter1Adapter(TaskAwaiter awaiter, string description) + sealed class TaskTupleAwaiter1Adapter(TaskAwaiter awaiter, string description) : AwaiterAdapter(description) { - private TaskAwaiter _awaiter = awaiter; + readonly TaskAwaiter _awaiter = awaiter; public override bool IsCompleted => _awaiter.IsCompleted; @@ -22,7 +22,7 @@ public override object[] GetResult() => [_awaiter.GetResult()]; } - private sealed class TaskTupleAwaiter2Adapter( + sealed class TaskTupleAwaiter2Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -42,7 +42,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter3Adapter( + sealed class TaskTupleAwaiter3Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -62,7 +62,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter4Adapter( + sealed class TaskTupleAwaiter4Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -82,7 +82,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter5Adapter( + sealed class TaskTupleAwaiter5Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -102,7 +102,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter6Adapter( + sealed class TaskTupleAwaiter6Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -122,7 +122,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter7Adapter( + sealed class TaskTupleAwaiter7Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -142,7 +142,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter8Adapter( + sealed class TaskTupleAwaiter8Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { @@ -162,7 +162,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter9Adapter( + sealed class TaskTupleAwaiter9Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -183,7 +183,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter10Adapter( + sealed class TaskTupleAwaiter10Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -204,7 +204,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter11Adapter( + sealed class TaskTupleAwaiter11Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -225,7 +225,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter12Adapter( + sealed class TaskTupleAwaiter12Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -247,7 +247,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter13Adapter( + sealed class TaskTupleAwaiter13Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -269,7 +269,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter14Adapter( + sealed class TaskTupleAwaiter14Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -294,7 +294,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter15Adapter( + sealed class TaskTupleAwaiter15Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) @@ -320,7 +320,7 @@ public override object[] GetResult() } } - private sealed class TaskTupleAwaiter16Adapter( + sealed class TaskTupleAwaiter16Adapter( TaskTupleExtensions.TupleTaskAwaiter awaiter, string description) : AwaiterAdapter(description) diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultConfiguredTaskAwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultConfiguredTaskAwaiterAdapter.cs index 4508c82..b38d4c5 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultConfiguredTaskAwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultConfiguredTaskAwaiterAdapter.cs @@ -4,11 +4,11 @@ namespace TaskTupleAwaiter.Tests.Adapters; partial class AwaiterAdapter { - private sealed class VoidResultConfiguredTaskAwaiterAdapter( + sealed class VoidResultConfiguredTaskAwaiterAdapter( ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter, string description) : AwaiterAdapter(description) { - private ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter = awaiter; + readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter = awaiter; public override bool IsCompleted => _awaiter.IsCompleted; diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultTaskAwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultTaskAwaiterAdapter.cs index 149d44a..4e2e73c 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultTaskAwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.VoidResultTaskAwaiterAdapter.cs @@ -4,10 +4,10 @@ namespace TaskTupleAwaiter.Tests.Adapters; partial class AwaiterAdapter { - private sealed class VoidResultTaskAwaiterAdapter(TaskAwaiter awaiter, string description) + sealed class VoidResultTaskAwaiterAdapter(TaskAwaiter awaiter, string description) : AwaiterAdapter(description) { - private TaskAwaiter _awaiter = awaiter; + readonly TaskAwaiter _awaiter = awaiter; public override bool IsCompleted => _awaiter.IsCompleted; diff --git a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.cs b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.cs index 754e840..9793192 100644 --- a/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.cs +++ b/test/TaskTupleAwaiter.Tests/Adapters/AwaiterAdapter.cs @@ -6,9 +6,9 @@ namespace TaskTupleAwaiter.Tests.Adapters; /// abstract partial class AwaiterAdapter { - private readonly string _description; + readonly string _description; - private AwaiterAdapter(string description) => + AwaiterAdapter(string description) => _description = description; public abstract bool IsCompleted { get; } @@ -59,24 +59,17 @@ public static IReadOnlyList CreateAdapters(Task[] tasks, CreateVoidResultTaskWhenAll(tasks, continueOnCapturedContext) ]; - public static AwaiterAdapter CreateTaskWhenAll(Task[] tasks, bool? continueOnCapturedContext = null) - { - if (continueOnCapturedContext != null) - return new ConfiguredTaskAwaiterAdapter( + public static AwaiterAdapter CreateTaskWhenAll(Task[] tasks, bool? continueOnCapturedContext = null) => continueOnCapturedContext != null + ? new ConfiguredTaskAwaiterAdapter( Task.WhenAll(tasks).ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter(), - $"await Task.WhenAll(…).ConfigureAwait({continueOnCapturedContext.Value})"); - return new TaskAwaiterAdapter(Task.WhenAll(tasks).GetAwaiter(), "await Task.WhenAll(…)"); - } + $"await Task.WhenAll(…).ConfigureAwait({continueOnCapturedContext.Value})") + : new TaskAwaiterAdapter(Task.WhenAll(tasks).GetAwaiter(), "await Task.WhenAll(…)"); - public static AwaiterAdapter CreateVoidResultTaskWhenAll(Task[] tasks, bool? continueOnCapturedContext = null) - { - if (continueOnCapturedContext != null) - return new VoidResultConfiguredTaskAwaiterAdapter( + public static AwaiterAdapter CreateVoidResultTaskWhenAll(Task[] tasks, bool? continueOnCapturedContext = null) => continueOnCapturedContext != null + ? new VoidResultConfiguredTaskAwaiterAdapter( Task.WhenAll(tasks).ConfigureAwait(continueOnCapturedContext.Value).GetAwaiter(), - $"await Task.WhenAll(…).ConfigureAwait({continueOnCapturedContext.Value})"); - - return new VoidResultTaskAwaiterAdapter(Task.WhenAll(tasks).GetAwaiter(), "await Task.WhenAll(…)"); - } + $"await Task.WhenAll(…).ConfigureAwait({continueOnCapturedContext.Value})") + : new VoidResultTaskAwaiterAdapter(Task.WhenAll(tasks).GetAwaiter(), "await Task.WhenAll(…)"); public static AwaiterAdapter CreateTaskTupleAwaiter(Task[] tasks, bool? continueOnCapturedContext = null) { diff --git a/test/TaskTupleAwaiter.Tests/BehaviorComparisonTests.cs b/test/TaskTupleAwaiter.Tests/BehaviorComparisonTests.cs index cd36d76..c93f1cf 100644 --- a/test/TaskTupleAwaiter.Tests/BehaviorComparisonTests.cs +++ b/test/TaskTupleAwaiter.Tests/BehaviorComparisonTests.cs @@ -11,9 +11,9 @@ static BehaviorComparisonTests() CanceledTask = canceled.Task; } - private static Task CompletedTask { get; } = Task.FromResult(null); - private static Task CanceledTask { get; } - private static Task FailedTask { get; } = TaskFromException(new DummyException()); + static Task CompletedTask { get; } = Task.FromResult(null); + static Task CanceledTask { get; } + static Task FailedTask { get; } = TaskFromException(new DummyException()); public static TheoryData EachArity() @@ -43,7 +43,7 @@ public static TheoryData EachIndexForEachArity() [Theory] [MemberData(nameof(EachIndexForEachArity))] - private static async Task WaitsForAllTasksToCompleteWhenAllSucceed(int arity, int whichToWaitFor) + static async Task WaitsForAllTasksToCompleteWhenAllSucceed(int arity, int whichToWaitFor) { TaskCompletionSource source = new(); @@ -65,7 +65,7 @@ private static async Task WaitsForAllTasksToCompleteWhenAllSucceed(int arity, in [Theory] [MemberData(nameof(EachIndexForEachArity))] - private static async Task WaitsForAllTasksToCompleteWhenAllCancel(int arity, int whichToWaitFor) + static async Task WaitsForAllTasksToCompleteWhenAllCancel(int arity, int whichToWaitFor) { TaskCompletionSource source = new(); @@ -91,7 +91,7 @@ private static async Task WaitsForAllTasksToCompleteWhenAllCancel(int arity, int [Theory] [MemberData(nameof(EachIndexForEachArity))] - private static async Task WaitsForAllTasksToCompleteWhenAllFail(int arity, int whichToWaitFor) + static async Task WaitsForAllTasksToCompleteWhenAllFail(int arity, int whichToWaitFor) { TaskCompletionSource source = new(); @@ -113,7 +113,7 @@ private static async Task WaitsForAllTasksToCompleteWhenAllFail(int arity, int w [Theory] [MemberData(nameof(EachArity))] - private static void CompletesSynchronouslyIfAllTasksWereCompletedSynchronously(int arity) + static void CompletesSynchronouslyIfAllTasksWereCompletedSynchronously(int arity) { Task[] tasks = [.. Enumerable.Repeat(CompletedTask, arity)]; @@ -124,7 +124,7 @@ private static void CompletesSynchronouslyIfAllTasksWereCompletedSynchronously(i [Theory] [MemberData(nameof(EachArity))] - private static void ResultsAreInCorrectOrder(int arity) + static void ResultsAreInCorrectOrder(int arity) { Task[] tasks = [.. Enumerable.Range(0, arity) .Select(index => Task.FromResult(index))]; @@ -146,7 +146,7 @@ private static void ResultsAreInCorrectOrder(int arity) #if !NET8_0_OR_GREATER [Theory] [MemberData(nameof(EachArity))] - private static void FirstExceptionIsThrown(int arity) + static void FirstExceptionIsThrown(int arity) { TaskCompletionSource[] sources = [.. Enumerable.Range(0, arity).Select(_ => new TaskCompletionSource())]; @@ -163,20 +163,20 @@ private static void FirstExceptionIsThrown(int arity) [Theory] [MemberData(nameof(EachArity))] - private static Task NonConfiguredAwaitUsesSynchronizationContext(int arity) => + static Task NonConfiguredAwaitUsesSynchronizationContext(int arity) => AssertUsesSynchronizationContext(arity, null, true); [Theory] [MemberData(nameof(EachArity))] - private static Task ConfigureAwaitTrueUsesSynchronizationContext(int arity) => + static Task ConfigureAwaitTrueUsesSynchronizationContext(int arity) => AssertUsesSynchronizationContext(arity, true, true); [Theory] [MemberData(nameof(EachArity))] - private static Task ConfigureAwaitFalseDoesNotUseSynchronizationContext(int arity) => + static Task ConfigureAwaitFalseDoesNotUseSynchronizationContext(int arity) => AssertUsesSynchronizationContext(arity, false, false); - private static async Task AssertUsesSynchronizationContext(int arity, bool? configureAwait, + static async Task AssertUsesSynchronizationContext(int arity, bool? configureAwait, bool shouldUseSynchronizationContext) { TaskCompletionSource source = new(); @@ -207,7 +207,7 @@ private static async Task AssertUsesSynchronizationContext(int arity, bool? conf } } - private static void AssertAllAdapters(IReadOnlyCollection adapters, + static void AssertAllAdapters(IReadOnlyCollection adapters, Func predicate) { #if NETFRAMEWORK @@ -221,14 +221,14 @@ private static void AssertAllAdapters(IReadOnlyCollection adapte adapters.Where(adapter => !predicate.Invoke(adapter)).ShouldBeEmpty(); } - private static Task TaskFromException(Exception exception) + static Task TaskFromException(Exception exception) { TaskCompletionSource source = new(); source.SetException(exception); return source.Task; } - private static IDisposable TempSyncContext(SynchronizationContext synchronizationContext) + static IDisposable TempSyncContext(SynchronizationContext synchronizationContext) { var previous = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(synchronizationContext); diff --git a/test/TaskTupleAwaiter.Tests/On.cs b/test/TaskTupleAwaiter.Tests/On.cs index 40be4b0..034143e 100644 --- a/test/TaskTupleAwaiter.Tests/On.cs +++ b/test/TaskTupleAwaiter.Tests/On.cs @@ -5,9 +5,9 @@ static class On public static IDisposable Dispose(Action action) => new OnDisposeAction(action); - private sealed class OnDisposeAction(Action action) : IDisposable + sealed class OnDisposeAction(Action action) : IDisposable { - private Action _action = action; + Action _action = action; public void Dispose() => Interlocked.Exchange(ref _action, null)?.Invoke(); diff --git a/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj b/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj index aa071fc..01ac4b3 100644 --- a/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj +++ b/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj @@ -1,8 +1,7 @@ - - - - + + runtime-async=on + diff --git a/test/TaskTupleAwaiter.Tests/TaskTupleAwaiterTests.cs b/test/TaskTupleAwaiter.Tests/TaskTupleAwaiterTests.cs index da59e2b..b752b88 100644 --- a/test/TaskTupleAwaiter.Tests/TaskTupleAwaiterTests.cs +++ b/test/TaskTupleAwaiter.Tests/TaskTupleAwaiterTests.cs @@ -244,7 +244,6 @@ async Task AwaitThreeTasksWaitsForAllOfThemWhenConfigured(bool continueOnCapture var awaitable = (task1, task2, slow.Task).ConfigureAwait(continueOnCapturedContext); awaitable.GetAwaiter().IsCompleted.ShouldBeFalse(); - slow.SetResult(42); await awaitable;