Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions Ix.NET/Documentation/adr/0002-System-Linq-Async-In-Net10.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ There are also a couple of cases where functionality simply has not been reprodu

`System.Linq.Async` also defined some interfaces that are not replicated in `System.Linq.AsyncEnumerable`. `System.Linq.Async` defined `IAsyncGrouping` to act as the return type for `GroupBy`. `System.Linq.AsyncEnumerable` just uses `IAsyncEnumerable<IGrouping<TKey, TElement>>`, which is not quite the same: this enables asynchronous iteration of the sequence of groups, but each invidual group's contents are not asynchronously enumerable. `IAsyncGrouping` enabled asynchronous enumeration of both. In practice, `System.Linq.Async` did not exploit this: it fully enumerated the whole source list to split items into groups before returning the first group, so although it compelled you to enumerate at both levels (e.g., with nested `await foreach` loops), in reality only the outer level was asynchronous in practice. So this interface added complication without real benefits. There is also `IAsyncIListProvider<T>`, an interface that arguably should not have been public in the first place, serving only to enable some internal optimizations. (Apparently it was public in `System.Linq.Async` because it is also used in other parts of Ix.NET.)

A further complication is that some methods in `System.Interactive.Async` clash with methods in `System.Linq.AsyncEnumerable`. For example, `MaxByAsync` and `MinByAsync`. Originally `MinBy` and `MaxBy` were unique to Rx.NET and Ix.NET. But .NET 6.0 added operators with these names to LINQ to Objects. Confusingly, they were slightly different: the Rx.NET and Ix.NET versions recognize that there might not be a single minimum or maximum value, and thus provide a collection of all the entries that are at the maximum value, but the .NET runtime class library versions just pick one arbitrary winner. So at this point, `System.Interactive` renamed its versions to `MinByWithTies` and `MaxByWithTies`. Unfortunately that same change wasn't made in `System.Interactive.Async`, so we now have the same situation with `System.Linq.AsyncEnumerable`: the .NET runtime class libraries now define `MinByAsync` and `MaxByAsync` extension methods for `IAsyncEnumerable<T>`, and these take the same arguments as the ones in `System.Interactive.Async`, but have a different return type, and have different behaviour!


## Decision

The next `System.Linq.Async` release will:
The next Ix.NET release will:

1. add a reference to `System.Linq.AsyncEnumerable` and `System.Interactive.Async`
2. remove from publicly visible API (ref assemblies) all `IAsyncEnumerable<T>` extension methods for which direct replacements exist
1. add a reference to `System.Linq.AsyncEnumerable` and `System.Interactive.Async` in `System.Linq.Async`
2. remove from `System.Linq.Async`'s and `System.Interactive.Async`'s publicly visible API (ref assemblies) all `IAsyncEnumerable<T>` extension methods for which direct replacements exist (adding `MinByWithTiesAsync` and `MaxByWithTiesAsync` for the case where the new .NET runtime library methods actually have slightly different functionality)
3. add [Obsolete] attribute for members of `AsyncEnumerable` for which `System.Linq.AsyncEnumerable` offers replacements that require code changes to use (e.g., `WhereAwait`, which is replaced by an overload of `Where`)
4. `AsyncEnumerable` methods that are a bad idea and that should probably have never existing (the ones that do sync over async, e.g. `ToEnumerable`) are marked as `Obsolete` and will not be replaced; note that although `ToObservable` has issues that meant the .NET team decided not to replicate it, the main issue is that it embeds opinions, and not that there's anything fundamentally broken about it, so we do not include `ToObservable` in this category
5. remaining methods of `AsyncEnumerable` (where `System.Linq.AsyncEnumerable` offers no equivalent) are removed from the publicly visible API of `System.Linq.Async`, with identical replacements being defined by `AsyncEnumerableEx` in `System.Interactive
Expand All @@ -60,6 +63,14 @@ In summary, each of the features previously provided by `System.Linq.Async` will
* Method hidden in `ref` assembly, available in `System.Interactive.Async`
* Method visible but marked as `Obsolete`, with new but slightly different equivalent available in `System.Linq.AsyncEnumerable`

### TFMs

We want to keep the TFMs for all Ix.NET packages exactly the same in this version, because the only reason for Ix.NET v7 to exist is to deal with the new existence of `System.Linq.AsyncEnumerable`.

There is one issue with this. If a project has a `net6.0` target and tries to use `System.Linq.AsyncEnumerable`, it produces a build warning, saying that it's not supported on that runtime. Although we don't like having this build warning, we are currently intending not to do anything about it, because we believe that messing with the TFMs is likely to have unintended consequences.

If it turns out that this does cause problems, we'll revisit this and do a new release.

## Consequences

Binary compatibility is maintained: any code that was built against `System.Linq.Async` v6 but which finds itself running against v7 at runtime should continue to work exactly as before.
Expand Down
1 change: 1 addition & 0 deletions Ix.NET/Source/ApiCompare/ApiCompare.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net10.0'">
Expand Down
4 changes: 4 additions & 0 deletions Ix.NET/Source/AsyncQueryableGenerator.t4
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ foreach (var m in asyncEnumerableType.GetMethods()
cast = "(" + toQuoted(m.ReturnType, null, 0) + ")";
}
}
else
{
continue; // this skips ToObservable, which we can't represent in a query
}
}

var n = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Optimize>true</Optimize>
<Configurations>Current Sources;Ix.net 3.1.1;Ix.net 3.2</Configurations>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions Ix.NET/Source/FasterLinq/FasterLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>

<NoWarn>$(NoWarn);IDE0007;IDE0034;IDE0040;IDE0063;IDE0090;IDE1006</NoWarn>
</PropertyGroup>
Expand Down
12 changes: 10 additions & 2 deletions Ix.NET/Source/Ix.NET.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32131.331
# Visual Studio Version 18
VisualStudioVersion = 18.0.11205.157 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{87534290-A7A6-47A4-9A3A-D0D21A9AD1D4}"
ProjectSection(SolutionItems) = preProject
Expand All @@ -11,6 +11,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B733D97A-F1ED-4FC3-BF8E-9AC47A89DE96}"
ProjectSection(SolutionItems) = preProject
..\..\.editorconfig = ..\..\.editorconfig
AsyncQueryableGenerator.t4 = AsyncQueryableGenerator.t4
..\..\azure-pipelines.ix.yml = ..\..\azure-pipelines.ix.yml
CodeCoverage.runsettings = CodeCoverage.runsettings
Directory.build.props = Directory.build.props
Expand Down Expand Up @@ -76,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Async.SourceGen
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.System.Interactive.ApiApprovals", "Tests.System.Interactive.ApiApprovals\Tests.System.Interactive.ApiApprovals.csproj", "{CD146918-6465-4D5B-B6B7-3F9803095EBD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Interactive.Async", "refs\System.Interactive.Async\System.Interactive.Async.csproj", "{74613134-0799-190D-7023-EC9A28B3A1F2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -156,6 +159,10 @@ Global
{CD146918-6465-4D5B-B6B7-3F9803095EBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD146918-6465-4D5B-B6B7-3F9803095EBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD146918-6465-4D5B-B6B7-3F9803095EBD}.Release|Any CPU.Build.0 = Release|Any CPU
{74613134-0799-190D-7023-EC9A28B3A1F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74613134-0799-190D-7023-EC9A28B3A1F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74613134-0799-190D-7023-EC9A28B3A1F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74613134-0799-190D-7023-EC9A28B3A1F2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -180,6 +187,7 @@ Global
{1754B36C-D0DB-4E5D-8C30-1F116046DC0F} = {A3D72E6E-4ADA-42E0-8B2A-055B1F244281}
{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4} = {80EFE3A1-1414-42EA-949B-1B5370A1B2EA}
{CD146918-6465-4D5B-B6B7-3F9803095EBD} = {87534290-A7A6-47A4-9A3A-D0D21A9AD1D4}
{74613134-0799-190D-7023-EC9A28B3A1F2} = {A3D72E6E-4ADA-42E0-8B2A-055B1F244281}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF70B0C6-C9D9-43B1-9BE4-08720EC1B7B7}
Expand Down
1 change: 1 addition & 0 deletions Ix.NET/Source/Playground/Playground.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading
Loading