Skip to content

Conversation

@idg10
Copy link
Collaborator

@idg10 idg10 commented Nov 13, 2025

Although the intent of System.Interactive.Async is to contain non-standard LINQ operators, with System.Linq.Async containing only standard ones, 'standard LINQ operators' is a somewhat nebulous concept. It turns out that since the original split between System.Interactive.Async and System.Linq.Async was established, System.Linq.AsyncEnumerable defines some methods that we had considered to be non-standard operators. (E.g., System.Interactive.Async offered projection-based forms of Distinct. But these have been superseded by the new standard DistinctBy.)

The result is that it turns out that in order to accommodate the arrival of System.Linq.AsyncEnumerable in .NET 10 (available to downlevel runtimes via NuGet), it is necessary not only to update System.Linq.Async (which we already did) but also to fix System.Interactive.Async.

Also, it turned out we had some issues with how we were building ref assemblies, and this fixes that too.

Having dealt with all the clashes in System.Linq.Async, it turns out that there are further methods in
We were picking up extra DLLs causing great confusion
@idg10 idg10 self-assigned this Nov 13, 2025
@idg10 idg10 marked this pull request as ready for review November 13, 2025 18:38
@idg10 idg10 merged commit 672f832 into main Nov 13, 2025
5 checks passed
@idg10 idg10 deleted the feature/2279-interactive-async-vs-asyncenumerable branch November 13, 2025 19:27
@idg10 idg10 requested a review from Copilot November 13, 2025 19:27
Copilot finished reviewing on behalf of idg10 November 13, 2025 19:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request addresses compatibility issues between System.Interactive.Async and the new System.Linq.AsyncEnumerable that ships in .NET 10, with the primary goal of ensuring both libraries can coexist without conflicts.

Key Changes

  • Added obsolete attributes to Distinct methods that are superseded by DistinctBy in System.Linq.AsyncEnumerable
  • Introduced MaxByWithTiesAsync and MinByWithTiesAsync to differentiate from the standard LINQ operators
  • Fixed reference assembly packaging to properly handle lib/ref mismatches across target frameworks

Reviewed Changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Ix.NET/Source/refs/System.Interactive.Async/System.Interactive.Async.csproj New reference assembly project with conditional System.Linq.AsyncEnumerable dependency for non-net10.0 targets
Ix.NET/Source/System.Interactive.Async/System.Interactive.Async.csproj Added reference assembly packaging and NuGet warning suppressions for intentional lib/ref mismatches
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Distinct.cs Marked projection-based Distinct overloads as obsolete, directing users to DistinctBy
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/MinByWithTies.cs New file implementing MinByWithTiesAsync to avoid clash with standard LINQ MinByAsync
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/MaxByWithTies.cs New file implementing MaxByWithTiesAsync to avoid clash with standard LINQ MaxByAsync
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/MinBy.cs Moved ExtremaBy helper methods to MinByWithTies.cs and wrapped in !REFERENCE_ASSEMBLY directive
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/MaxBy.cs Wrapped in !REFERENCE_ASSEMBLY directive; implementation now shares helpers with MaxByWithTies.cs
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Min.cs Wrapped in !REFERENCE_ASSEMBLY directive to exclude from reference assembly
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Max.cs Wrapped in !REFERENCE_ASSEMBLY directive to exclude from reference assembly
Ix.NET/Source/System.Linq.Async/System.Linq.Async.csproj Fixed reference assembly packaging path pattern
Ix.NET/Source/System.Interactive/System.Interactive.csproj Fixed reference assembly packaging path pattern
Ix.NET/Source/System.Interactive.Providers/System.Interactive.Providers.csproj Fixed reference assembly packaging path pattern
Ix.NET/Source/System.Interactive.Async.Providers/System/Linq/AsyncQueryableEx.Generated.cs Added query provider overloads for AsAsyncEnumerable, AverageAsync, MinByWithTiesAsync, and MaxByWithTiesAsync
Ix.NET/Source/System.Linq.Async.SourceGenerator/AsyncOverloadsGenerator.cs Fixed generator to skip empty groupings
Ix.NET/Source/AsyncQueryableGenerator.t4 Added code to skip ToObservable method which cannot be represented in queries
Ix.NET/Source/Playground/Playground.csproj Marked as non-packable
Ix.NET/Source/FasterLinq/FasterLinq.csproj Marked as non-packable
Ix.NET/Source/Benchmarks.System.Interactive/Benchmarks.System.Interactive.csproj Marked as non-packable
Ix.NET/Source/ApiCompare/ApiCompare.csproj Marked as non-packable
Ix.NET/Source/Ix.NET.sln Added reference assembly project to solution, updated Visual Studio version
Ix.NET/Source/Tests.System.Interactive.ApiApprovals/Api/ApiApprovalTests.SystemInteractiveAsync.verified.cs Updated API approval tests with obsolete attributes and new methods
Ix.NET/Source/Tests.System.Interactive.ApiApprovals/Api/ApiApprovalTests.SystemInteractiveAsyncProviders.verified.cs Updated API approval tests with new query provider methods
Ix.NET/Source/System.Interactive.Async.Tests/System/Linq/Operators/MinByWithTies.cs New test file for MinByWithTiesAsync
Ix.NET/Source/System.Interactive.Async.Tests/System/Linq/Operators/MaxByWithTies.cs New test file for MaxByWithTiesAsync
Ix.NET/Documentation/adr/0002-System-Linq-Async-In-Net10.md Updated ADR with explanation of the clashes and resolution strategy

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +70
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and" +
" the functionality of selector-based overloads of Distinct now exists as Distinc" +
"tBy.")]
public static System.Collections.Generic.IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this System.Collections.Generic.IAsyncEnumerable<TSource> source, System.Func<TSource, System.Threading.Tasks.ValueTask<TKey>> keySelector) { }
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and" +
" the functionality of selector-based overloads of Distinct now exists as Distinc" +
"tBy.")]
public static System.Collections.Generic.IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this System.Collections.Generic.IAsyncEnumerable<TSource> source, System.Func<TSource, TKey> keySelector) { }
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and" +
" the functionality of selector-based overloads of Distinct now exists as Distinc" +
"tBy.")]
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'DistinctBy' (was split as 'Distinc' + 'tBy' due to line break).

Suggested change
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and" +
" the functionality of selector-based overloads of Distinct now exists as Distinc" +
"tBy.")]
public static System.Collections.Generic.IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this System.Collections.Generic.IAsyncEnumerable<TSource> source, System.Func<TSource, System.Threading.Tasks.ValueTask<TKey>> keySelector) { }
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and" +
" the functionality of selector-based overloads of Distinct now exists as Distinc" +
"tBy.")]
public static System.Collections.Generic.IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this System.Collections.Generic.IAsyncEnumerable<TSource> source, System.Func<TSource, TKey> keySelector) { }
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and" +
" the functionality of selector-based overloads of Distinct now exists as Distinc" +
"tBy.")]
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the functionality of selector-based overloads of Distinct now exists as DistinctBy.")]
public static System.Collections.Generic.IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this System.Collections.Generic.IAsyncEnumerable<TSource> source, System.Func<TSource, System.Threading.Tasks.ValueTask<TKey>> keySelector) { }
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the functionality of selector-based overloads of Distinct now exists as DistinctBy.")]
public static System.Collections.Generic.IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this System.Collections.Generic.IAsyncEnumerable<TSource> source, System.Func<TSource, TKey> keySelector) { }
[System.Obsolete("Use DistinctBy. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the functionality of selector-based overloads of Distinct now exists as DistinctBy.")]

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants