-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Add a GroupJoin overload without a result selector, returning an IGrouping #120587
Copy link
Copy link
Open
Open
Copy link
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Linq
Milestone
Description
Motivation and Background
GroupJoin currently forces users to pass in a result selector which determines the shape of the return type. In most basic usages, users project out to an anonymous type (or a tuple) which wraps the returned TInner and TOuters - this proposal adds an additional overload of GroupJoin that removes to need to specify a result selector, and returns a tuple. We expect this will cover and simplify a large majority of the use-cases.
This proposal mirrors the simplified Zip overload, and also #120596 for Join.
API Proposal
namespace System.Linq;
public static partial class Enumerable
{
public static IEnumerable<IGrouping<TOuter, TInner>> GroupJoin<TOuter, TInner, TKey>(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
IEqualityComparer<TKey>? comparer = null);
}
public static partial class Queryable
{
public static IQueryable<IGrouping<TOuter, TInner>> GroupJoin<TOuter, TInner, TKey>(
this IQueryable<TOuter> outer,
IEnumerable<TInner> inner,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
IEqualityComparer<TKey>? comparer = null);
}
public static class AsyncEnumerable
{
public static IAsyncEnumerable<IGrouping<TOuter, TInner>> GroupJoin<TOuter, TInner, TKey>(
this IAsyncEnumerable<TOuter> outer,
IAsyncEnumerable<TInner> inner,
Func<TOuter, CancellationToken, ValueTask<TKey>> outerKeySelector,
Func<TInner, CancellationToken, ValueTask<TKey>> innerKeySelector,
IEqualityComparer<TKey>? comparer = null);
public static IAsyncEnumerable<IGrouping<TOuter, TInner>> Join<TOuter, TInner, TKey>(
this IAsyncEnumerable<TOuter> outer,
IAsyncEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
IEqualityComparer<TKey>? comparer = null);
}Usage
var departments = new List<Department>
{
new() { Id = 1, Name = "HR" },
new() { Id = 2, Name = "IT" },
new() { Id = 3, Name = "Finance" }
};
var employees = new List<Employee>
{
new() { Name = "Alice", DepartmentId = 1 },
new() { Name = "Bob", DepartmentId = 2 },
new() { Name = "Charlie", DepartmentId = 2 },
new() { Name = "David", DepartmentId = 3 }
};
// New proposed simplified usage:
foreach (var (emp, depts) in employees.GroupJoin(departments, e => e.DepartmentId, d => d.Id))
{
Console.WriteLine($"Employee: {emp.Name}, departments: {string.Join(", ", depts.Select(i => i.Name))}");
}
// Existing usage: explicit projection out to tuple required
foreach (var (emp, depts) in employees.GroupJoin(departments, e => e.DepartmentId, d => d.Id, (e, d) => (e, d)))
{
Console.WriteLine($"Employee: {emp.Name}, departments: {string.Join(", ", depts.Select(i => i.Name))}");
}
class Department
{
public int Id { get; set; }
public string Name { get; set; }
}
class Employee
{
public string Name { get; set; }
public int DepartmentId { get; set; }
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Linq