diff --git a/apiCount.include.md b/apiCount.include.md index 7603962d..3045d82e 100644 --- a/apiCount.include.md +++ b/apiCount.include.md @@ -1,4 +1,4 @@ -**API count: 978** +**API count: 986** ### Per Target Framework @@ -22,7 +22,7 @@ | `net6.0` | 512 | | `net7.0` | 359 | | `net8.0` | 240 | -| `net9.0` | 151 | -| `net10.0` | 98 | +| `net9.0` | 164 | +| `net10.0` | 111 | | `net11.0` | 58 | | `uap10.0` | 937 | diff --git a/api_list.include.md b/api_list.include.md index 0ae9d53a..2cf78fb6 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -340,6 +340,7 @@ #### EqualityComparer * `EqualityComparer Create(Func, Func?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.create?view=net-11.0) + * `EqualityComparer Create(Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.create?view=net-11.0#system-collections-generic-equalitycomparer-1-create-1(system-func((-0-0))-system-collections-generic-iequalitycomparer((-0)))) #### EventInfo @@ -499,10 +500,16 @@ #### IEnumerable + * `IEnumerable FullJoin(IEnumerable, Func, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.fulljoin?view=net-11.0#system-linq-enumerable-fulljoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2)))) + * `IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.fulljoin?view=net-11.0#system-linq-enumerable-fulljoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) + * `IEnumerable> GroupJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupjoin?view=net-11.0#system-linq-enumerable-groupjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) + * `IEnumerable<(TOuter Outer, TInner Inner)> Join(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.join?view=net-11.0#system-linq-enumerable-join-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable LeftJoin(IEnumerable, Func, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable LeftJoin(IEnumerable, Func, Func, Func)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3)))) + * `IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable RightJoin(IEnumerable, Func, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable RightJoin(IEnumerable, Func, Func, Func)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3)))) + * `IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) #### IEnumerable @@ -1237,6 +1244,7 @@ * `bool IsAssignableFrom()` * `bool IsAssignableTo(Type?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.type.isassignableto?view=net-11.0) * `bool IsAssignableTo()` + * `Type? GetNullableUnderlyingType()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.type.getnullableunderlyingtype?view=net-11.0) * `IsGenericMethodParameter` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.type.isgenericmethodparameter?view=net-11.0) diff --git a/assemblySize.include.md b/assemblySize.include.md index 9a5d82ff..644d918d 100644 --- a/assemblySize.include.md +++ b/assemblySize.include.md @@ -20,8 +20,8 @@ | net6.0 | 10.0KB | 181.5KB | +171.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | | net7.0 | 10.0KB | 144.0KB | +134.0KB | +9.0KB | +5.0KB | +512bytes | +3.0KB | | net8.0 | 9.5KB | 115.0KB | +105.5KB | +8.5KB | | +512bytes | +3.5KB | -| net9.0 | 9.5KB | 69.0KB | +59.5KB | +8.5KB | | +512bytes | +3.5KB | -| net10.0 | 10.0KB | 45.5KB | +35.5KB | +8.5KB | | +512bytes | +3.0KB | +| net9.0 | 9.5KB | 72.0KB | +62.5KB | +8.5KB | | +512bytes | +3.5KB | +| net10.0 | 10.0KB | 48.5KB | +38.5KB | +9.0KB | | +1.0KB | +3.5KB | | net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB | @@ -47,6 +47,6 @@ | net6.0 | 10.0KB | 264.4KB | +254.4KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | | net7.0 | 10.0KB | 208.3KB | +198.3KB | +16.6KB | +6.4KB | +1.1KB | +3.7KB | | net8.0 | 9.5KB | 164.0KB | +154.5KB | +16.0KB | +299bytes | +1.1KB | +4.2KB | -| net9.0 | 9.5KB | 96.5KB | +87.0KB | +16.0KB | | +1.1KB | +4.2KB | -| net10.0 | 10.0KB | 64.1KB | +54.1KB | +16.0KB | | +1.1KB | +3.7KB | +| net9.0 | 9.5KB | 100.7KB | +91.2KB | +16.0KB | | +1.1KB | +4.2KB | +| net10.0 | 10.0KB | 68.3KB | +58.3KB | +16.5KB | | +1.6KB | +4.2KB | | net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB | diff --git a/readme.md b/readme.md index 25271ead..93424ad5 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ The package targets `netstandard2.0` and is designed to support the following ru * `uap10` -**API count: 978** +**API count: 986** ### Per Target Framework @@ -37,8 +37,8 @@ The package targets `netstandard2.0` and is designed to support the following ru | `net6.0` | 512 | | `net7.0` | 359 | | `net8.0` | 240 | -| `net9.0` | 151 | -| `net10.0` | 98 | +| `net9.0` | 164 | +| `net10.0` | 111 | | `net11.0` | 58 | | `uap10.0` | 937 | @@ -114,8 +114,8 @@ This project uses features from the newest stable SDK and C# language. As such c | net6.0 | 10.0KB | 181.5KB | +171.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | | net7.0 | 10.0KB | 144.0KB | +134.0KB | +9.0KB | +5.0KB | +512bytes | +3.0KB | | net8.0 | 9.5KB | 115.0KB | +105.5KB | +8.5KB | | +512bytes | +3.5KB | -| net9.0 | 9.5KB | 69.0KB | +59.5KB | +8.5KB | | +512bytes | +3.5KB | -| net10.0 | 10.0KB | 45.5KB | +35.5KB | +8.5KB | | +512bytes | +3.0KB | +| net9.0 | 9.5KB | 72.0KB | +62.5KB | +8.5KB | | +512bytes | +3.5KB | +| net10.0 | 10.0KB | 48.5KB | +38.5KB | +9.0KB | | +1.0KB | +3.5KB | | net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB | @@ -141,8 +141,8 @@ This project uses features from the newest stable SDK and C# language. As such c | net6.0 | 10.0KB | 264.4KB | +254.4KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | | net7.0 | 10.0KB | 208.3KB | +198.3KB | +16.6KB | +6.4KB | +1.1KB | +3.7KB | | net8.0 | 9.5KB | 164.0KB | +154.5KB | +16.0KB | +299bytes | +1.1KB | +4.2KB | -| net9.0 | 9.5KB | 96.5KB | +87.0KB | +16.0KB | | +1.1KB | +4.2KB | -| net10.0 | 10.0KB | 64.1KB | +54.1KB | +16.0KB | | +1.1KB | +3.7KB | +| net9.0 | 9.5KB | 100.7KB | +91.2KB | +16.0KB | | +1.1KB | +4.2KB | +| net10.0 | 10.0KB | 68.3KB | +58.3KB | +16.5KB | | +1.6KB | +4.2KB | | net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB | @@ -871,6 +871,7 @@ The class `Polyfill` includes the following extension methods: #### EqualityComparer * `EqualityComparer Create(Func, Func?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.create?view=net-11.0) + * `EqualityComparer Create(Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.create?view=net-11.0#system-collections-generic-equalitycomparer-1-create-1(system-func((-0-0))-system-collections-generic-iequalitycomparer((-0)))) #### EventInfo @@ -1030,10 +1031,16 @@ The class `Polyfill` includes the following extension methods: #### IEnumerable + * `IEnumerable FullJoin(IEnumerable, Func, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.fulljoin?view=net-11.0#system-linq-enumerable-fulljoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2)))) + * `IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.fulljoin?view=net-11.0#system-linq-enumerable-fulljoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) + * `IEnumerable> GroupJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupjoin?view=net-11.0#system-linq-enumerable-groupjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) + * `IEnumerable<(TOuter Outer, TInner Inner)> Join(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.join?view=net-11.0#system-linq-enumerable-join-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable LeftJoin(IEnumerable, Func, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable LeftJoin(IEnumerable, Func, Func, Func)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3)))) + * `IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable RightJoin(IEnumerable, Func, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2)))) * `IEnumerable RightJoin(IEnumerable, Func, Func, Func)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3)))) + * `IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin(IEnumerable, Func, Func, IEqualityComparer?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2)))) #### IEnumerable @@ -1768,6 +1775,7 @@ The class `Polyfill` includes the following extension methods: * `bool IsAssignableFrom()` * `bool IsAssignableTo(Type?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.type.isassignableto?view=net-11.0) * `bool IsAssignableTo()` + * `Type? GetNullableUnderlyingType()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.type.getnullableunderlyingtype?view=net-11.0) * `IsGenericMethodParameter` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.type.isgenericmethodparameter?view=net-11.0) diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index c2d71864..7fa0b4fb 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -783,6 +783,8 @@ void EqualityComparer_Methods() { var comparer = EqualityComparer.Create((x, y) => x == y, x => x); comparer = EqualityComparer.Create((x, y) => x == y); + comparer = EqualityComparer.Create(x => x); + comparer = EqualityComparer.Create(x => x, EqualityComparer.Default); } void Enum_Methods() @@ -1069,12 +1071,25 @@ void IEnumerable_Methods() IEnumerable lengths = [1]; var intersectBy = enumerable.IntersectBy(lengths, _ => _.Length); var intersectByComparer = enumerable.IntersectBy(lengths, _ => _.Length, EqualityComparer.Default); + IEnumerable joinInner = ["a", "b"]; + var fullJoin = enumerable.FullJoin(joinInner, _ => _, _ => _, (o, i) => $"{o}-{i}"); + var fullJoinComparer = enumerable.FullJoin(joinInner, _ => _, _ => _, (o, i) => $"{o}-{i}", StringComparer.OrdinalIgnoreCase); + var groupJoin = enumerable.GroupJoin(joinInner, _ => _, _ => _); + var groupJoinComparer = enumerable.GroupJoin(joinInner, _ => _, _ => _, StringComparer.OrdinalIgnoreCase); #if FeatureValueTuple IEnumerable<(string Key, int Value)> inner = [("a", 1)]; var leftJoin = enumerable.LeftJoin(inner, _ => _, _ => _.Key, (o, i) => $"{o}-{i.Value}"); var leftJoinComparer = enumerable.LeftJoin(inner, _ => _, _ => _.Key, (o, i) => $"{o}-{i.Value}", StringComparer.OrdinalIgnoreCase); var rightJoin = enumerable.RightJoin(inner, _ => _, _ => _.Key, (o, i) => $"{o}-{i.Value}"); var rightJoinComparer = enumerable.RightJoin(inner, _ => _, _ => _.Key, (o, i) => $"{o}-{i.Value}", StringComparer.OrdinalIgnoreCase); + var joinTuple = enumerable.Join(inner, _ => _, _ => _.Key); + var joinTupleComparer = enumerable.Join(inner, _ => _, _ => _.Key, StringComparer.OrdinalIgnoreCase); + var leftJoinTuple = enumerable.LeftJoin(inner, _ => _, _ => _.Key); + var leftJoinTupleComparer = enumerable.LeftJoin(inner, _ => _, _ => _.Key, StringComparer.OrdinalIgnoreCase); + var rightJoinTuple = enumerable.RightJoin(inner, _ => _, _ => _.Key); + var rightJoinTupleComparer = enumerable.RightJoin(inner, _ => _, _ => _.Key, StringComparer.OrdinalIgnoreCase); + var fullJoinTuple = enumerable.FullJoin(inner, _ => _, _ => _.Key); + var fullJoinTupleComparer = enumerable.FullJoin(inner, _ => _, _ => _.Key, StringComparer.OrdinalIgnoreCase); #endif } @@ -1729,6 +1744,7 @@ void Type_Methods(MemberInfo info) var result = typeof(List).IsAssignableTo(typeof(string)); result = typeof(List).IsAssignableTo(null); result = typeof(string).IsGenericMethodParameter; + var underlying = typeof(int?).GetNullableUnderlyingType(); var member = typeof(string).GetMemberWithSameMetadataDefinitionAs(info); } diff --git a/src/Polyfill/EqualityComparerPolyfill.cs b/src/Polyfill/EqualityComparerPolyfill.cs index 2200a614..046f7115 100644 --- a/src/Polyfill/EqualityComparerPolyfill.cs +++ b/src/Polyfill/EqualityComparerPolyfill.cs @@ -1,5 +1,3 @@ -#if !NET8_0_OR_GREATER - namespace Polyfills; using System; @@ -7,6 +5,7 @@ namespace Polyfills; static partial class Polyfill { +#if !NET8_0_OR_GREATER class DelegateEqualityComparer(Func equals, Func? getHashCode) : EqualityComparer { @@ -27,6 +26,34 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } -} +#endif + +#if !NET11_0_OR_GREATER + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.create?view=net-11.0#system-collections-generic-equalitycomparer-1-create-1(system-func((-0-0))-system-collections-generic-iequalitycomparer((-0))) + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } #endif +} diff --git a/src/Polyfill/Polyfill_IEnumerable_FullJoin.cs b/src/Polyfill/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..3742ce0b --- /dev/null +++ b/src/Polyfill/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,89 @@ +#if !NET11_0_OR_GREATER + +namespace Polyfills; + +using System; +using System.Collections.Generic; +using System.Linq; + +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.fulljoin?view=net-11.0#system-linq-enumerable-fulljoin-4(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-1-3))-system-collections-generic-iequalitycomparer((-2))) + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } + +#if FeatureValueTuple + + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.fulljoin?view=net-11.0#system-linq-enumerable-fulljoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2))) + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); + +#endif +} + +#endif diff --git a/src/Polyfill/Polyfill_IEnumerable_GroupJoin.cs b/src/Polyfill/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..f10ae647 --- /dev/null +++ b/src/Polyfill/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,45 @@ +#if !NET11_0_OR_GREATER + +namespace Polyfills; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupjoin?view=net-11.0#system-linq-enumerable-groupjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2))) + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} + +#endif diff --git a/src/Polyfill/Polyfill_IEnumerable_Join.cs b/src/Polyfill/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..02899a85 --- /dev/null +++ b/src/Polyfill/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,25 @@ +#if FeatureValueTuple && !NET11_0_OR_GREATER + +namespace Polyfills; + +using System; +using System.Collections.Generic; +using System.Linq; + +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.join?view=net-11.0#system-linq-enumerable-join-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2))) + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} + +#endif diff --git a/src/Polyfill/Polyfill_IEnumerable_LeftJoin.cs b/src/Polyfill/Polyfill_IEnumerable_LeftJoin.cs index d2bf484f..3bfe9f07 100644 --- a/src/Polyfill/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Polyfill/Polyfill_IEnumerable_LeftJoin.cs @@ -1,5 +1,3 @@ -#if !NET10_0_OR_GREATER - namespace Polyfills; using System; @@ -8,6 +6,8 @@ namespace Polyfills; static partial class Polyfill { +#if !NET10_0_OR_GREATER + /// /// Correlates the elements of two sequences based on matching keys. /// The default equality comparer is used to compare keys. @@ -63,6 +63,23 @@ static IEnumerable LeftJoinIterator( } } } -} #endif + +#if FeatureValueTuple && !NET11_0_OR_GREATER + + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.leftjoin?view=net-11.0#system-linq-enumerable-leftjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2))) + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); + +#endif +} diff --git a/src/Polyfill/Polyfill_IEnumerable_RightJoin.cs b/src/Polyfill/Polyfill_IEnumerable_RightJoin.cs index 7b8a71c1..b5f23807 100644 --- a/src/Polyfill/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Polyfill/Polyfill_IEnumerable_RightJoin.cs @@ -1,5 +1,3 @@ -#if !NET10_0_OR_GREATER - namespace Polyfills; using System; @@ -8,6 +6,8 @@ namespace Polyfills; static partial class Polyfill { +#if !NET10_0_OR_GREATER + /// /// Correlates the elements of two sequences based on matching keys. /// The default equality comparer is used to compare keys. @@ -63,6 +63,23 @@ static IEnumerable RightJoinIterator( } } } -} #endif + +#if FeatureValueTuple && !NET11_0_OR_GREATER + + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.rightjoin?view=net-11.0#system-linq-enumerable-rightjoin-3(system-collections-generic-ienumerable((-0))-system-collections-generic-ienumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-collections-generic-iequalitycomparer((-2))) + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); + +#endif +} diff --git a/src/Polyfill/TypePolyfill.cs b/src/Polyfill/TypePolyfill.cs index 212e0674..b2ed5804 100644 --- a/src/Polyfill/TypePolyfill.cs +++ b/src/Polyfill/TypePolyfill.cs @@ -1,10 +1,10 @@ -#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER namespace Polyfills; using System; static partial class Polyfill { +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER extension(Type target) { /// @@ -15,5 +15,17 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } -} #endif + +#if !NET11_0_OR_GREATER + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.type.getnullableunderlyingtype?view=net-11.0 + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +#endif +} diff --git a/src/Split/net10.0/EqualityComparerPolyfill.cs b/src/Split/net10.0/EqualityComparerPolyfill.cs new file mode 100644 index 00000000..41245747 --- /dev/null +++ b/src/Split/net10.0/EqualityComparerPolyfill.cs @@ -0,0 +1,30 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +static partial class Polyfill +{ + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } +} diff --git a/src/Split/net10.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net10.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net10.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net10.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net10.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net10.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net10.0/Polyfill_IEnumerable_Join.cs b/src/Split/net10.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net10.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net10.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net10.0/Polyfill_IEnumerable_LeftJoin.cs new file mode 100644 index 00000000..3e82bf3f --- /dev/null +++ b/src/Split/net10.0/Polyfill_IEnumerable_LeftJoin.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net10.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net10.0/Polyfill_IEnumerable_RightJoin.cs new file mode 100644 index 00000000..f2eeba41 --- /dev/null +++ b/src/Split/net10.0/Polyfill_IEnumerable_RightJoin.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net10.0/TypePolyfill.cs b/src/Split/net10.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/net10.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/net461/EqualityComparerPolyfill.cs b/src/Split/net461/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net461/EqualityComparerPolyfill.cs +++ b/src/Split/net461/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net461/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net461/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net461/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net461/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net461/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net461/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net461/Polyfill_IEnumerable_Join.cs b/src/Split/net461/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net461/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net461/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net461/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net461/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net461/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net461/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net461/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net461/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net461/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net461/TypePolyfill.cs b/src/Split/net461/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net461/TypePolyfill.cs +++ b/src/Split/net461/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net462/EqualityComparerPolyfill.cs b/src/Split/net462/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net462/EqualityComparerPolyfill.cs +++ b/src/Split/net462/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net462/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net462/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net462/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net462/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net462/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net462/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net462/Polyfill_IEnumerable_Join.cs b/src/Split/net462/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net462/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net462/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net462/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net462/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net462/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net462/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net462/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net462/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net462/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net462/TypePolyfill.cs b/src/Split/net462/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net462/TypePolyfill.cs +++ b/src/Split/net462/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net47/EqualityComparerPolyfill.cs b/src/Split/net47/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net47/EqualityComparerPolyfill.cs +++ b/src/Split/net47/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net47/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net47/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net47/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net47/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net47/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net47/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net47/Polyfill_IEnumerable_Join.cs b/src/Split/net47/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net47/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net47/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net47/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net47/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net47/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net47/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net47/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net47/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net47/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net47/TypePolyfill.cs b/src/Split/net47/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net47/TypePolyfill.cs +++ b/src/Split/net47/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net471/EqualityComparerPolyfill.cs b/src/Split/net471/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net471/EqualityComparerPolyfill.cs +++ b/src/Split/net471/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net471/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net471/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net471/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net471/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net471/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net471/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net471/Polyfill_IEnumerable_Join.cs b/src/Split/net471/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net471/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net471/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net471/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net471/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net471/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net471/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net471/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net471/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net471/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net471/TypePolyfill.cs b/src/Split/net471/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net471/TypePolyfill.cs +++ b/src/Split/net471/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net472/EqualityComparerPolyfill.cs b/src/Split/net472/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net472/EqualityComparerPolyfill.cs +++ b/src/Split/net472/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net472/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net472/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net472/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net472/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net472/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net472/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net472/Polyfill_IEnumerable_Join.cs b/src/Split/net472/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net472/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net472/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net472/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net472/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net472/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net472/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net472/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net472/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net472/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net472/TypePolyfill.cs b/src/Split/net472/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net472/TypePolyfill.cs +++ b/src/Split/net472/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net48/EqualityComparerPolyfill.cs b/src/Split/net48/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net48/EqualityComparerPolyfill.cs +++ b/src/Split/net48/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net48/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net48/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net48/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net48/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net48/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net48/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net48/Polyfill_IEnumerable_Join.cs b/src/Split/net48/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net48/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net48/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net48/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net48/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net48/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net48/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net48/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net48/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net48/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net48/TypePolyfill.cs b/src/Split/net48/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net48/TypePolyfill.cs +++ b/src/Split/net48/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net481/EqualityComparerPolyfill.cs b/src/Split/net481/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net481/EqualityComparerPolyfill.cs +++ b/src/Split/net481/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net481/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net481/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net481/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net481/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net481/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net481/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net481/Polyfill_IEnumerable_Join.cs b/src/Split/net481/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net481/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net481/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net481/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net481/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net481/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net481/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net481/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net481/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net481/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net481/TypePolyfill.cs b/src/Split/net481/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/net481/TypePolyfill.cs +++ b/src/Split/net481/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/net5.0/EqualityComparerPolyfill.cs b/src/Split/net5.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net5.0/EqualityComparerPolyfill.cs +++ b/src/Split/net5.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net5.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net5.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net5.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net5.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net5.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net5.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net5.0/Polyfill_IEnumerable_Join.cs b/src/Split/net5.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net5.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net5.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net5.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net5.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net5.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net5.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net5.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net5.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net5.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net5.0/TypePolyfill.cs b/src/Split/net5.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/net5.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/net6.0/EqualityComparerPolyfill.cs b/src/Split/net6.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net6.0/EqualityComparerPolyfill.cs +++ b/src/Split/net6.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net6.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net6.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net6.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net6.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net6.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net6.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net6.0/Polyfill_IEnumerable_Join.cs b/src/Split/net6.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net6.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net6.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net6.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net6.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net6.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net6.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net6.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net6.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net6.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net6.0/TypePolyfill.cs b/src/Split/net6.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/net6.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/net7.0/EqualityComparerPolyfill.cs b/src/Split/net7.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/net7.0/EqualityComparerPolyfill.cs +++ b/src/Split/net7.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/net7.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net7.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net7.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net7.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net7.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net7.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net7.0/Polyfill_IEnumerable_Join.cs b/src/Split/net7.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net7.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net7.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net7.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net7.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net7.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net7.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net7.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net7.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net7.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net7.0/TypePolyfill.cs b/src/Split/net7.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/net7.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/net8.0/EqualityComparerPolyfill.cs b/src/Split/net8.0/EqualityComparerPolyfill.cs new file mode 100644 index 00000000..41245747 --- /dev/null +++ b/src/Split/net8.0/EqualityComparerPolyfill.cs @@ -0,0 +1,30 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +static partial class Polyfill +{ + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } +} diff --git a/src/Split/net8.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net8.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net8.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net8.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net8.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net8.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net8.0/Polyfill_IEnumerable_Join.cs b/src/Split/net8.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net8.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net8.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net8.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net8.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net8.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net8.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net8.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net8.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net8.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net8.0/TypePolyfill.cs b/src/Split/net8.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/net8.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/net9.0/EqualityComparerPolyfill.cs b/src/Split/net9.0/EqualityComparerPolyfill.cs new file mode 100644 index 00000000..41245747 --- /dev/null +++ b/src/Split/net9.0/EqualityComparerPolyfill.cs @@ -0,0 +1,30 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +static partial class Polyfill +{ + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } +} diff --git a/src/Split/net9.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/net9.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/net9.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/net9.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/net9.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/net9.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/net9.0/Polyfill_IEnumerable_Join.cs b/src/Split/net9.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/net9.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/net9.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/net9.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/net9.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/net9.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net9.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/net9.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/net9.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/net9.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/net9.0/TypePolyfill.cs b/src/Split/net9.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/net9.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/netcoreapp2.0/EqualityComparerPolyfill.cs b/src/Split/netcoreapp2.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netcoreapp2.0/EqualityComparerPolyfill.cs +++ b/src/Split/netcoreapp2.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_Join.cs b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netcoreapp2.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netcoreapp2.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp2.0/TypePolyfill.cs b/src/Split/netcoreapp2.0/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/netcoreapp2.0/TypePolyfill.cs +++ b/src/Split/netcoreapp2.0/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/netcoreapp2.1/EqualityComparerPolyfill.cs b/src/Split/netcoreapp2.1/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netcoreapp2.1/EqualityComparerPolyfill.cs +++ b/src/Split/netcoreapp2.1/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_Join.cs b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netcoreapp2.1/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netcoreapp2.1/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp2.1/TypePolyfill.cs b/src/Split/netcoreapp2.1/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/netcoreapp2.1/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/netcoreapp2.2/EqualityComparerPolyfill.cs b/src/Split/netcoreapp2.2/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netcoreapp2.2/EqualityComparerPolyfill.cs +++ b/src/Split/netcoreapp2.2/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_Join.cs b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netcoreapp2.2/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netcoreapp2.2/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp2.2/TypePolyfill.cs b/src/Split/netcoreapp2.2/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/netcoreapp2.2/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/netcoreapp3.0/EqualityComparerPolyfill.cs b/src/Split/netcoreapp3.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netcoreapp3.0/EqualityComparerPolyfill.cs +++ b/src/Split/netcoreapp3.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_Join.cs b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netcoreapp3.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netcoreapp3.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp3.0/TypePolyfill.cs b/src/Split/netcoreapp3.0/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/netcoreapp3.0/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/netcoreapp3.1/EqualityComparerPolyfill.cs b/src/Split/netcoreapp3.1/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netcoreapp3.1/EqualityComparerPolyfill.cs +++ b/src/Split/netcoreapp3.1/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_Join.cs b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netcoreapp3.1/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netcoreapp3.1/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netcoreapp3.1/TypePolyfill.cs b/src/Split/netcoreapp3.1/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/netcoreapp3.1/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/netstandard2.0/EqualityComparerPolyfill.cs b/src/Split/netstandard2.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netstandard2.0/EqualityComparerPolyfill.cs +++ b/src/Split/netstandard2.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netstandard2.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netstandard2.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netstandard2.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netstandard2.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netstandard2.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netstandard2.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netstandard2.0/Polyfill_IEnumerable_Join.cs b/src/Split/netstandard2.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netstandard2.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netstandard2.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netstandard2.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netstandard2.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netstandard2.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netstandard2.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netstandard2.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netstandard2.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netstandard2.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netstandard2.0/TypePolyfill.cs b/src/Split/netstandard2.0/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/netstandard2.0/TypePolyfill.cs +++ b/src/Split/netstandard2.0/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Split/netstandard2.1/EqualityComparerPolyfill.cs b/src/Split/netstandard2.1/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/netstandard2.1/EqualityComparerPolyfill.cs +++ b/src/Split/netstandard2.1/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/netstandard2.1/Polyfill_IEnumerable_FullJoin.cs b/src/Split/netstandard2.1/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/netstandard2.1/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/netstandard2.1/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/netstandard2.1/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/netstandard2.1/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/netstandard2.1/Polyfill_IEnumerable_Join.cs b/src/Split/netstandard2.1/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/netstandard2.1/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/netstandard2.1/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/netstandard2.1/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/netstandard2.1/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/netstandard2.1/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netstandard2.1/Polyfill_IEnumerable_RightJoin.cs b/src/Split/netstandard2.1/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/netstandard2.1/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/netstandard2.1/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/netstandard2.1/TypePolyfill.cs b/src/Split/netstandard2.1/TypePolyfill.cs new file mode 100644 index 00000000..f3230ba0 --- /dev/null +++ b/src/Split/netstandard2.1/TypePolyfill.cs @@ -0,0 +1,15 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +static partial class Polyfill +{ + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } +} diff --git a/src/Split/uap10.0/EqualityComparerPolyfill.cs b/src/Split/uap10.0/EqualityComparerPolyfill.cs index ca1787e2..e45f2a30 100644 --- a/src/Split/uap10.0/EqualityComparerPolyfill.cs +++ b/src/Split/uap10.0/EqualityComparerPolyfill.cs @@ -22,4 +22,26 @@ public static EqualityComparer Create( Func? getHashCode = null) => new DelegateEqualityComparer(equals, getHashCode); } + class KeySelectorEqualityComparer(Func keySelector, IEqualityComparer? keyComparer) + : EqualityComparer + { + readonly IEqualityComparer comparer = keyComparer ?? EqualityComparer.Default; + public override bool Equals(T? x, T? y) => + comparer.Equals(keySelector(x)!, keySelector(y)!); + public override int GetHashCode(T obj) + { + var key = keySelector(obj); + return key is null ? 0 : comparer.GetHashCode(key); + } + } + extension(EqualityComparer) + { + /// + /// Creates an that determines equality by projecting each value through and comparing the resulting keys. + /// + public static EqualityComparer Create( + Func keySelector, + IEqualityComparer? keyComparer = null) => + new KeySelectorEqualityComparer(keySelector, keyComparer); + } } diff --git a/src/Split/uap10.0/Polyfill_IEnumerable_FullJoin.cs b/src/Split/uap10.0/Polyfill_IEnumerable_FullJoin.cs new file mode 100644 index 00000000..5677f8f8 --- /dev/null +++ b/src/Split/uap10.0/Polyfill_IEnumerable_FullJoin.cs @@ -0,0 +1,74 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// A specified is used to compare keys. + /// + public static IEnumerable FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer = null) => + FullJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + static IEnumerable FullJoinIterator( + IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector, + IEqualityComparer? comparer) + { + comparer ??= EqualityComparer.Default; + var innerLookup = inner.ToLookup(innerKeySelector, comparer); + var outerKeys = new HashSet(comparer); + foreach (var outerElement in outer) + { + var key = outerKeySelector(outerElement); + outerKeys.Add(key); + var innerGroup = innerLookup[key]; + var hasMatch = false; + foreach (var innerElement in innerGroup) + { + hasMatch = true; + yield return resultSelector(outerElement, innerElement); + } + if (!hasMatch) + { + yield return resultSelector(outerElement, default); + } + } + foreach (var innerGroup in innerLookup) + { + if (outerKeys.Contains(innerGroup.Key)) + { + continue; + } + foreach (var innerElement in innerGroup) + { + yield return resultSelector(default, innerElement); + } + } + } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of each sequence that have no matching element in the other. + /// + public static IEnumerable<(TOuter? Outer, TInner? Inner)> FullJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.FullJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif +} diff --git a/src/Split/uap10.0/Polyfill_IEnumerable_GroupJoin.cs b/src/Split/uap10.0/Polyfill_IEnumerable_GroupJoin.cs new file mode 100644 index 00000000..03a139d0 --- /dev/null +++ b/src/Split/uap10.0/Polyfill_IEnumerable_GroupJoin.cs @@ -0,0 +1,37 @@ +// +#pragma warning disable +namespace Polyfills; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on key equality and groups the results. + /// Each result is an keyed by the outer element. + /// A specified is used to compare keys. + /// + public static IEnumerable> GroupJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.GroupJoin( + outer, + inner, + outerKeySelector, + innerKeySelector, + (outerElement, innerElements) => (IGrouping)new GroupJoinGrouping(outerElement, innerElements), + comparer); + sealed class GroupJoinGrouping(TKey key, IEnumerable elements) : + IGrouping + { + public TKey Key => key; + public IEnumerator GetEnumerator() => + elements.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + } +} diff --git a/src/Split/uap10.0/Polyfill_IEnumerable_Join.cs b/src/Split/uap10.0/Polyfill_IEnumerable_Join.cs new file mode 100644 index 00000000..f2c39ae7 --- /dev/null +++ b/src/Split/uap10.0/Polyfill_IEnumerable_Join.cs @@ -0,0 +1,22 @@ +// +#pragma warning disable +#if FeatureValueTuple +namespace Polyfills; +using System; +using System.Collections.Generic; +using System.Linq; +static partial class Polyfill +{ + /// + /// Correlates the elements of two sequences based on matching keys. + /// A specified is used to compare keys. + /// + public static IEnumerable<(TOuter Outer, TInner Inner)> Join( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + Enumerable.Join(outer, inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +} +#endif diff --git a/src/Split/uap10.0/Polyfill_IEnumerable_LeftJoin.cs b/src/Split/uap10.0/Polyfill_IEnumerable_LeftJoin.cs index 102f3be8..f05abcdf 100644 --- a/src/Split/uap10.0/Polyfill_IEnumerable_LeftJoin.cs +++ b/src/Split/uap10.0/Polyfill_IEnumerable_LeftJoin.cs @@ -54,4 +54,17 @@ static IEnumerable LeftJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the outer sequence that have no matching inner element. + /// + public static IEnumerable<(TOuter Outer, TInner? Inner)> LeftJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.LeftJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/uap10.0/Polyfill_IEnumerable_RightJoin.cs b/src/Split/uap10.0/Polyfill_IEnumerable_RightJoin.cs index ee0d692e..d26a761c 100644 --- a/src/Split/uap10.0/Polyfill_IEnumerable_RightJoin.cs +++ b/src/Split/uap10.0/Polyfill_IEnumerable_RightJoin.cs @@ -54,4 +54,17 @@ static IEnumerable RightJoinIterator( } } } +#if FeatureValueTuple + /// + /// Correlates the elements of two sequences based on matching keys, + /// including elements of the inner sequence that have no matching outer element. + /// + public static IEnumerable<(TOuter? Outer, TInner Inner)> RightJoin( + this IEnumerable outer, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + IEqualityComparer? comparer = null) => + outer.RightJoin(inner, outerKeySelector, innerKeySelector, (outerElement, innerElement) => (outerElement, innerElement), comparer); +#endif } diff --git a/src/Split/uap10.0/TypePolyfill.cs b/src/Split/uap10.0/TypePolyfill.cs index 38e7b04f..b5fbd6c6 100644 --- a/src/Split/uap10.0/TypePolyfill.cs +++ b/src/Split/uap10.0/TypePolyfill.cs @@ -13,4 +13,12 @@ static partial class Polyfill target.IsGenericParameter && target.DeclaringMethod != null; } + extension(Type target) + { + /// + /// Returns the underlying type argument of the current nullable value type, or null if the current type is not a nullable value type. + /// + public Type? GetNullableUnderlyingType() => + Nullable.GetUnderlyingType(target); + } } diff --git a/src/Tests/EqualityComparerPolyfillTests.cs b/src/Tests/EqualityComparerPolyfillTests.cs index a8c52174..74e8f0c7 100644 --- a/src/Tests/EqualityComparerPolyfillTests.cs +++ b/src/Tests/EqualityComparerPolyfillTests.cs @@ -26,4 +26,23 @@ public async Task Create_WithEqualsOnly() await Assert.That(comparer.Equals(5, 4)).IsFalse(); await Assert.That(() => comparer.GetHashCode(5)).Throws(); } + + [Test] + public async Task Create_WithKeySelector() + { + var comparer = EqualityComparer.Create(value => value!.Length); + + await Assert.That(comparer.Equals("abc", "xyz")).IsTrue(); + await Assert.That(comparer.Equals("abc", "ab")).IsFalse(); + await Assert.That(comparer.GetHashCode("abc")).IsEqualTo(comparer.GetHashCode("xyz")); + } + + [Test] + public async Task Create_WithKeySelectorAndComparer() + { + var comparer = EqualityComparer.Create(value => value, StringComparer.OrdinalIgnoreCase); + + await Assert.That(comparer.Equals("ABC", "abc")).IsTrue(); + await Assert.That(comparer.Equals("ABC", "xyz")).IsFalse(); + } } diff --git a/src/Tests/PolyfillTests_IEnumerable.cs b/src/Tests/PolyfillTests_IEnumerable.cs index 6226d76c..73e52b1c 100644 --- a/src/Tests/PolyfillTests_IEnumerable.cs +++ b/src/Tests/PolyfillTests_IEnumerable.cs @@ -531,4 +531,150 @@ public async Task IEnumerableRightJoinWithComparer() await Assert.That(result[1]).IsEqualTo("B-2"); await Assert.That(result[2]).IsEqualTo("-3"); } + + [Test] + public async Task IEnumerableJoinTuple() + { + IEnumerable people = ["Terry", "Charlotte", "Tom"]; + IEnumerable<(string Name, string Owner)> pets = + [ + ("Barley", "Terry"), + ("Boots", "Terry"), + ("Whiskers", "Charlotte"), + ("Daisy", "Magnus") + ]; + + var result = people.Join( + pets, + person => person, + pet => pet.Owner).ToList(); + + await Assert.That(result).Count().IsEqualTo(3); + await Assert.That(result[0].Outer).IsEqualTo("Terry"); + await Assert.That(result[0].Inner.Name).IsEqualTo("Barley"); + await Assert.That(result[1].Inner.Name).IsEqualTo("Boots"); + await Assert.That(result[2].Outer).IsEqualTo("Charlotte"); + await Assert.That(result[2].Inner.Name).IsEqualTo("Whiskers"); + } + + [Test] + public async Task IEnumerableLeftJoinTuple() + { + IEnumerable people = ["Magnus", "Terry", "Tom"]; + IEnumerable<(string Name, string Owner)> pets = + [ + ("Daisy", "Magnus"), + ("Barley", "Terry") + ]; + + var result = people.LeftJoin( + pets, + person => person, + pet => pet.Owner).ToList(); + + await Assert.That(result).Count().IsEqualTo(3); + await Assert.That(result[0].Outer).IsEqualTo("Magnus"); + await Assert.That(result[0].Inner.Name).IsEqualTo("Daisy"); + await Assert.That(result[1].Outer).IsEqualTo("Terry"); + await Assert.That(result[1].Inner.Name).IsEqualTo("Barley"); + await Assert.That(result[2].Outer).IsEqualTo("Tom"); + await Assert.That(result[2].Inner.Name).IsNull(); + } + + [Test] + public async Task IEnumerableRightJoinTuple() + { + IEnumerable people = ["Terry", "Charlotte"]; + IEnumerable<(string Name, string Owner)> pets = + [ + ("Barley", "Terry"), + ("Whiskers", "Charlotte"), + ("Daisy", "Magnus") + ]; + + var result = people.RightJoin( + pets, + person => person, + pet => pet.Owner).ToList(); + + await Assert.That(result).Count().IsEqualTo(3); + await Assert.That(result[0].Outer).IsEqualTo("Terry"); + await Assert.That(result[0].Inner.Name).IsEqualTo("Barley"); + await Assert.That(result[1].Outer).IsEqualTo("Charlotte"); + await Assert.That(result[2].Outer).IsNull(); + await Assert.That(result[2].Inner.Name).IsEqualTo("Daisy"); + } + + [Test] + public async Task IEnumerableFullJoin() + { + IEnumerable people = ["Magnus", "Terry", "Tom"]; + IEnumerable<(string Name, string Owner)> pets = + [ + ("Daisy", "Magnus"), + ("Barley", "Terry"), + ("Stray", "Nobody") + ]; + + var result = people.FullJoin( + pets, + person => person, + pet => pet.Owner, + (person, pet) => $"{person ?? "?"}-{pet.Name ?? "?"}").ToList(); + + await Assert.That(result).Count().IsEqualTo(4); + await Assert.That(result[0]).IsEqualTo("Magnus-Daisy"); + await Assert.That(result[1]).IsEqualTo("Terry-Barley"); + await Assert.That(result[2]).IsEqualTo("Tom-?"); + await Assert.That(result[3]).IsEqualTo("?-Stray"); + } + + [Test] + public async Task IEnumerableFullJoinTuple() + { + IEnumerable people = ["Magnus", "Tom"]; + IEnumerable<(string Name, string Owner)> pets = + [ + ("Daisy", "Magnus"), + ("Stray", "Nobody") + ]; + + var result = people.FullJoin( + pets, + person => person, + pet => pet.Owner).ToList(); + + await Assert.That(result).Count().IsEqualTo(3); + await Assert.That(result[0].Outer).IsEqualTo("Magnus"); + await Assert.That(result[0].Inner.Name).IsEqualTo("Daisy"); + await Assert.That(result[1].Outer).IsEqualTo("Tom"); + await Assert.That(result[1].Inner.Name).IsNull(); + await Assert.That(result[2].Outer).IsNull(); + await Assert.That(result[2].Inner.Name).IsEqualTo("Stray"); + } + + [Test] + public async Task IEnumerableGroupJoinTuple() + { + IEnumerable people = ["Magnus", "Terry", "Tom"]; + IEnumerable<(string Name, string Owner)> pets = + [ + ("Daisy", "Magnus"), + ("Barley", "Terry"), + ("Boots", "Terry") + ]; + + var result = people.GroupJoin( + pets, + person => person, + pet => pet.Owner).ToList(); + + await Assert.That(result).Count().IsEqualTo(3); + await Assert.That(result[0].Key).IsEqualTo("Magnus"); + await Assert.That(result[0].Single().Name).IsEqualTo("Daisy"); + await Assert.That(result[1].Key).IsEqualTo("Terry"); + await Assert.That(result[1].Count()).IsEqualTo(2); + await Assert.That(result[2].Key).IsEqualTo("Tom"); + await Assert.That(result[2].Any()).IsFalse(); + } } diff --git a/src/Tests/PolyfillTests_Type.cs b/src/Tests/PolyfillTests_Type.cs index 138b98c2..41fdf583 100644 --- a/src/Tests/PolyfillTests_Type.cs +++ b/src/Tests/PolyfillTests_Type.cs @@ -31,6 +31,14 @@ public async Task IsGenericMethodParameter() await Assert.That(genericParam.IsGenericMethodParameter).IsTrue(); } + [Test] + public async Task GetNullableUnderlyingType() + { + await Assert.That(typeof(int?).GetNullableUnderlyingType()).IsEqualTo(typeof(int)); + await Assert.That(typeof(int).GetNullableUnderlyingType()).IsNull(); + await Assert.That(typeof(string).GetNullableUnderlyingType()).IsNull(); + } + [Test] public async Task IsAssignableTo() {