From 1473dded869fff41b96cffc0eec81256461ff2c9 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 12 Nov 2022 01:11:11 +0100 Subject: [PATCH 1/4] Update "Lookup" & "Grouping" to .NET 7 versions --- MoreLinq/Lookup.cs | 300 ++++++++++++++++++++++++--------------------- 1 file changed, 163 insertions(+), 137 deletions(-) diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 5373f28b7..5fd124224 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -24,257 +24,283 @@ // SOFTWARE. #endregion -#nullable disable +#if !NET6_0_OR_GREATER +#nullable enable annotations +#pragma warning disable 8602 // Dereference of a possibly null reference. +#pragma warning disable 8603 // Possible null reference return. +#endif namespace MoreLinq { using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Linq; /// /// A implementation that preserves insertion order /// - /// The type of the keys in the - /// The type of the elements in the sequences that make up the values in the /// - /// This implementation preserves insertion order of keys and elements within each - /// Copied over from CoreFX on 2015-10-27 - /// https://github.com/dotnet/corefx/blob/6f1c2a86fb8fa1bdaee7c6e70a684d27842d804c/src/System.Linq/src/System/Linq/Enumerable.cs#L3230-L3403 - /// Modified to remove internal interfaces + /// This implementation preserves insertion order of keys and elements within each . Copied and modified from + /// Lookup.cs /// - internal class Lookup : IEnumerable>, ILookup + + [DebuggerDisplay("Count = {Count}")] + sealed class Lookup : ILookup { - private IEqualityComparer _comparer; + private readonly IEqualityComparer _comparer; private Grouping[] _groupings; - private Grouping _lastGrouping; + private Grouping? _lastGrouping; private int _count; - internal static Lookup Create(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + internal static Lookup Create(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); + Debug.Assert(source != null); + Debug.Assert(keySelector != null); + Debug.Assert(elementSelector != null); + Lookup lookup = new Lookup(comparer); - foreach (TSource item in source) { - lookup.GetGrouping(keySelector(item), true).Add(elementSelector(item)); + foreach (TSource item in source) + { + lookup.GetGrouping(keySelector(item), create: true)!.Add(elementSelector(item)); } + return lookup; } - internal static Lookup CreateForJoin(IEnumerable source, Func keySelector, IEqualityComparer comparer) + internal static Lookup Create(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { + Debug.Assert(source != null); + Debug.Assert(keySelector != null); + Lookup lookup = new Lookup(comparer); - foreach (TElement item in source) { - TKey key = keySelector(item); - if (key != null) lookup.GetGrouping(key, true).Add(item); + foreach (TElement item in source) + { + lookup.GetGrouping(keySelector(item), create: true)!.Add(item); } + return lookup; } - private Lookup(IEqualityComparer comparer) + internal static Lookup CreateForJoin(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { - if (comparer == null) comparer = EqualityComparer.Default; - _comparer = comparer; - _groupings = new Grouping[7]; + Lookup lookup = new Lookup(comparer); + foreach (TElement item in source) + { + TKey key = keySelector(item); + if (key != null) + { + lookup.GetGrouping(key, create: true)!.Add(item); + } + } + + return lookup; } - public int Count + private Lookup(IEqualityComparer? comparer) { - get { return _count; } + _comparer = comparer ?? EqualityComparer.Default; + _groupings = new Grouping[7]; } + public int Count => _count; + public IEnumerable this[TKey key] { get { - Grouping grouping = GetGrouping(key, false); - if (grouping != null) return grouping; - return Enumerable.Empty(); + Grouping? grouping = GetGrouping(key, create: false); + return grouping ?? Enumerable.Empty(); } } - public bool Contains(TKey key) - { - return _count > 0 && GetGrouping(key, false) != null; - } + public bool Contains(TKey key) => GetGrouping(key, create: false) != null; public IEnumerator> GetEnumerator() { - Grouping g = _lastGrouping; - if (g != null) { - do { - g = g.next; - yield return g; - } while (g != _lastGrouping); - } - } + Grouping? g = _lastGrouping; + if (g != null) + { + do + { + g = g._next; - public IEnumerable ApplyResultSelector(Func, TResult> resultSelector) - { - Grouping g = _lastGrouping; - if (g != null) { - do { - g = g.next; - if (g.count != g.elements.Length) { Array.Resize(ref g.elements, g.count); } - yield return resultSelector(g.key, g.elements); - } while (g != _lastGrouping); + Debug.Assert(g != null); + yield return g; + } + while (g != _lastGrouping); } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal int InternalGetHashCode(TKey key) + private int InternalGetHashCode(TKey key) { // Handle comparer implementations that throw when passed null return (key == null) ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; } - internal Grouping GetGrouping(TKey key, bool create) + internal Grouping? GetGrouping(TKey key, bool create) { int hashCode = InternalGetHashCode(key); - for (Grouping g = _groupings[hashCode % _groupings.Length]; g != null; g = g.hashNext) - if (g.hashCode == hashCode && _comparer.Equals(g.key, key)) return g; - if (create) { - if (_count == _groupings.Length) Resize(); + for (Grouping? g = _groupings[hashCode % _groupings.Length]; g != null; g = g._hashNext) + { + if (g._hashCode == hashCode && _comparer.Equals(g._key, key)) + { + return g; + } + } + + if (create) + { + if (_count == _groupings.Length) + { + Resize(); + } + int index = hashCode % _groupings.Length; - Grouping g = new Grouping(); - g.key = key; - g.hashCode = hashCode; - g.elements = new TElement[1]; - g.hashNext = _groupings[index]; + Grouping g = new Grouping(key, hashCode); + g._hashNext = _groupings[index]; _groupings[index] = g; - if (_lastGrouping == null) { - g.next = g; + if (_lastGrouping == null) + { + g._next = g; } - else { - g.next = _lastGrouping.next; - _lastGrouping.next = g; + else + { + g._next = _lastGrouping._next; + _lastGrouping._next = g; } + _lastGrouping = g; _count++; return g; } + return null; } private void Resize() { - int newSize = checked(_count * 2 + 1); + int newSize = checked((_count * 2) + 1); Grouping[] newGroupings = new Grouping[newSize]; - Grouping g = _lastGrouping; - do { - g = g.next; - int index = g.hashCode % newSize; - g.hashNext = newGroupings[index]; + Grouping g = _lastGrouping!; + do + { + g = g._next!; + int index = g._hashCode % newSize; + g._hashNext = newGroupings[index]; newGroupings[index] = g; - } while (g != _lastGrouping); + } + while (g != _lastGrouping); + _groupings = newGroupings; } } - internal class Grouping : IGrouping, IList + // Modified from: + // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Linq/src/System/Linq/Grouping.cs#L48-L141 + + [DebuggerDisplay("Key = {Key}")] + sealed class Grouping : IGrouping, IList { - internal TKey key; - internal int hashCode; - internal TElement[] elements; - internal int count; - internal Grouping hashNext; - internal Grouping next; - - internal Grouping() + internal readonly TKey _key; + internal readonly int _hashCode; + internal TElement[] _elements; + internal int _count; + internal Grouping? _hashNext; + internal Grouping? _next; + + internal Grouping(TKey key, int hashCode) { + _key = key; + _hashCode = hashCode; + _elements = new TElement[1]; } internal void Add(TElement element) { - if (elements.Length == count) Array.Resize(ref elements, checked(count * 2)); - elements[count] = element; - count++; + if (_elements.Length == _count) + { + Array.Resize(ref _elements, checked(_count * 2)); + } + + _elements[_count] = element; + _count++; } - public IEnumerator GetEnumerator() + internal void Trim() { - for (int i = 0; i < count; i++) yield return elements[i]; + if (_elements.Length != _count) + { + Array.Resize(ref _elements, _count); + } } - IEnumerator IEnumerable.GetEnumerator() + public IEnumerator GetEnumerator() { - return GetEnumerator(); + for (int i = 0; i < _count; i++) + { + yield return _elements[i]; + } } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + // DDB195907: implement IGrouping<>.Key implicitly // so that WPF binding works on this property. - public TKey Key - { - get { return key; } - } + public TKey Key => _key; - int ICollection.Count - { - get { return count; } - } + int ICollection.Count => _count; - bool ICollection.IsReadOnly - { - get { return true; } - } + bool ICollection.IsReadOnly => true; - void ICollection.Add(TElement item) - { - throw new NotSupportedException("Lookup is immutable"); - } + void ICollection.Add(TElement item) => ThrowHelper.ThrowNotSupportedException(); - void ICollection.Clear() - { - throw new NotSupportedException("Lookup is immutable"); - } + void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); - bool ICollection.Contains(TElement item) - { - return Array.IndexOf(elements, item, 0, count) >= 0; - } + bool ICollection.Contains(TElement item) => Array.IndexOf(_elements, item, 0, _count) >= 0; - void ICollection.CopyTo(TElement[] array, int arrayIndex) - { - Array.Copy(elements, 0, array, arrayIndex, count); - } + void ICollection.CopyTo(TElement[] array, int arrayIndex) => + Array.Copy(_elements, 0, array, arrayIndex, _count); bool ICollection.Remove(TElement item) { - throw new NotSupportedException("Lookup is immutable"); + ThrowHelper.ThrowNotSupportedException(); + return false; } - int IList.IndexOf(TElement item) - { - return Array.IndexOf(elements, item, 0, count); - } + int IList.IndexOf(TElement item) => Array.IndexOf(_elements, item, 0, _count); - void IList.Insert(int index, TElement item) - { - throw new NotSupportedException("Lookup is immutable"); - } + void IList.Insert(int index, TElement item) => ThrowHelper.ThrowNotSupportedException(); - void IList.RemoveAt(int index) - { - throw new NotSupportedException("Lookup is immutable"); - } + void IList.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException(); TElement IList.this[int index] { get { - if (index < 0 || index >= count) throw new ArgumentOutOfRangeException(nameof(index)); - return elements[index]; + if (index < 0 || index >= _count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _elements[index]; } + set { - throw new NotSupportedException("Lookup is immutable"); + ThrowHelper.ThrowNotSupportedException(); } } + + static class ThrowHelper + { + [DoesNotReturn] + internal static void ThrowNotSupportedException() => throw new NotSupportedException("Grouping is immutable."); + } } } From a29994154e97fb1c2425fd1d3a3b2372f50daf3b Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 12 Nov 2022 01:23:17 +0100 Subject: [PATCH 2/4] Use project conventions --- MoreLinq/Lookup.cs | 111 ++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 67 deletions(-) diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 5fd124224..09628cfc1 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -49,58 +49,54 @@ namespace MoreLinq /// [DebuggerDisplay("Count = {Count}")] - sealed class Lookup : ILookup + internal sealed class Lookup : ILookup { - private readonly IEqualityComparer _comparer; - private Grouping[] _groupings; - private Grouping? _lastGrouping; - private int _count; + readonly IEqualityComparer _comparer; + Grouping[] _groupings; + Grouping? _lastGrouping; + int _count; internal static Lookup Create(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) { - Debug.Assert(source != null); - Debug.Assert(keySelector != null); - Debug.Assert(elementSelector != null); + Debug.Assert(source is not null); + Debug.Assert(keySelector is not null); + Debug.Assert(elementSelector is not null); - Lookup lookup = new Lookup(comparer); - foreach (TSource item in source) - { + var lookup = new Lookup(comparer); + + foreach (var item in source) lookup.GetGrouping(keySelector(item), create: true)!.Add(elementSelector(item)); - } return lookup; } internal static Lookup Create(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { - Debug.Assert(source != null); - Debug.Assert(keySelector != null); + Debug.Assert(source is not null); + Debug.Assert(keySelector is not null); - Lookup lookup = new Lookup(comparer); - foreach (TElement item in source) - { + var lookup = new Lookup(comparer); + + foreach (var item in source) lookup.GetGrouping(keySelector(item), create: true)!.Add(item); - } return lookup; } internal static Lookup CreateForJoin(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { - Lookup lookup = new Lookup(comparer); - foreach (TElement item in source) + var lookup = new Lookup(comparer); + + foreach (var item in source) { - TKey key = keySelector(item); - if (key != null) - { + if (keySelector(item) is { } key) lookup.GetGrouping(key, create: true)!.Add(item); - } } return lookup; } - private Lookup(IEqualityComparer? comparer) + Lookup(IEqualityComparer? comparer) { _comparer = comparer ?? EqualityComparer.Default; _groupings = new Grouping[7]; @@ -112,23 +108,23 @@ public IEnumerable this[TKey key] { get { - Grouping? grouping = GetGrouping(key, create: false); + var grouping = GetGrouping(key, create: false); return grouping ?? Enumerable.Empty(); } } - public bool Contains(TKey key) => GetGrouping(key, create: false) != null; + public bool Contains(TKey key) => GetGrouping(key, create: false) is not null; public IEnumerator> GetEnumerator() { - Grouping? g = _lastGrouping; - if (g != null) + var g = _lastGrouping; + if (g is not null) { do { g = g._next; - Debug.Assert(g != null); + Debug.Assert(g is not null); yield return g; } while (g != _lastGrouping); @@ -137,21 +133,17 @@ public IEnumerator> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private int InternalGetHashCode(TKey key) - { + int InternalGetHashCode(TKey key) => // Handle comparer implementations that throw when passed null - return (key == null) ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; - } + key is null ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; internal Grouping? GetGrouping(TKey key, bool create) { - int hashCode = InternalGetHashCode(key); - for (Grouping? g = _groupings[hashCode % _groupings.Length]; g != null; g = g._hashNext) + var hashCode = InternalGetHashCode(key); + for (var g = _groupings[hashCode % _groupings.Length]; g is not null; g = g._hashNext) { if (g._hashCode == hashCode && _comparer.Equals(g._key, key)) - { return g; - } } if (create) @@ -161,11 +153,11 @@ private int InternalGetHashCode(TKey key) Resize(); } - int index = hashCode % _groupings.Length; - Grouping g = new Grouping(key, hashCode); + var index = hashCode % _groupings.Length; + var g = new Grouping(key, hashCode); g._hashNext = _groupings[index]; _groupings[index] = g; - if (_lastGrouping == null) + if (_lastGrouping is null) { g._next = g; } @@ -183,15 +175,15 @@ private int InternalGetHashCode(TKey key) return null; } - private void Resize() + void Resize() { - int newSize = checked((_count * 2) + 1); - Grouping[] newGroupings = new Grouping[newSize]; - Grouping g = _lastGrouping!; + var newSize = checked((_count * 2) + 1); + var newGroupings = new Grouping[newSize]; + var g = _lastGrouping!; do { g = g._next!; - int index = g._hashCode % newSize; + var index = g._hashCode % newSize; g._hashNext = newGroupings[index]; newGroupings[index] = g; } @@ -205,7 +197,7 @@ private void Resize() // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Linq/src/System/Linq/Grouping.cs#L48-L141 [DebuggerDisplay("Key = {Key}")] - sealed class Grouping : IGrouping, IList + internal sealed class Grouping : IGrouping, IList { internal readonly TKey _key; internal readonly int _hashCode; @@ -224,9 +216,7 @@ internal Grouping(TKey key, int hashCode) internal void Add(TElement element) { if (_elements.Length == _count) - { Array.Resize(ref _elements, checked(_count * 2)); - } _elements[_count] = element; _count++; @@ -235,17 +225,13 @@ internal void Add(TElement element) internal void Trim() { if (_elements.Length != _count) - { Array.Resize(ref _elements, _count); - } } public IEnumerator GetEnumerator() { - for (int i = 0; i < _count; i++) - { + for (var i = 0; i < _count; i++) yield return _elements[i]; - } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -281,20 +267,11 @@ bool ICollection.Remove(TElement item) TElement IList.this[int index] { - get - { - if (index < 0 || index >= _count) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return _elements[index]; - } + get => index < 0 || index >= _count + ? throw new ArgumentOutOfRangeException(nameof(index)) + : _elements[index]; - set - { - ThrowHelper.ThrowNotSupportedException(); - } + set => ThrowHelper.ThrowNotSupportedException(); } static class ThrowHelper From 19879c309bc251914fa5a8c50514dab7e058159f Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 12 Nov 2022 01:25:01 +0100 Subject: [PATCH 3/4] Remove "ThrowHelper" --- MoreLinq/Lookup.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 09628cfc1..df067bdc1 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -244,9 +244,9 @@ public IEnumerator GetEnumerator() bool ICollection.IsReadOnly => true; - void ICollection.Add(TElement item) => ThrowHelper.ThrowNotSupportedException(); + void ICollection.Add(TElement item) => ThrowModificationNotSupportedException(); - void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); + void ICollection.Clear() => ThrowModificationNotSupportedException(); bool ICollection.Contains(TElement item) => Array.IndexOf(_elements, item, 0, _count) >= 0; @@ -255,15 +255,15 @@ void ICollection.CopyTo(TElement[] array, int arrayIndex) => bool ICollection.Remove(TElement item) { - ThrowHelper.ThrowNotSupportedException(); + ThrowModificationNotSupportedException(); return false; } int IList.IndexOf(TElement item) => Array.IndexOf(_elements, item, 0, _count); - void IList.Insert(int index, TElement item) => ThrowHelper.ThrowNotSupportedException(); + void IList.Insert(int index, TElement item) => ThrowModificationNotSupportedException(); - void IList.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException(); + void IList.RemoveAt(int index) => ThrowModificationNotSupportedException(); TElement IList.this[int index] { @@ -271,13 +271,10 @@ TElement IList.this[int index] ? throw new ArgumentOutOfRangeException(nameof(index)) : _elements[index]; - set => ThrowHelper.ThrowNotSupportedException(); + set => ThrowModificationNotSupportedException(); } - static class ThrowHelper - { - [DoesNotReturn] - internal static void ThrowNotSupportedException() => throw new NotSupportedException("Grouping is immutable."); - } + [DoesNotReturn] + static void ThrowModificationNotSupportedException() => throw new NotSupportedException("Grouping is immutable."); } } From 35e20e533b37685669cab69c85fe70e7bf0da121 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 12 Nov 2022 01:26:29 +0100 Subject: [PATCH 4/4] Group unsupported members together --- MoreLinq/Lookup.cs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index df067bdc1..8ee430a64 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -244,27 +244,13 @@ public IEnumerator GetEnumerator() bool ICollection.IsReadOnly => true; - void ICollection.Add(TElement item) => ThrowModificationNotSupportedException(); - - void ICollection.Clear() => ThrowModificationNotSupportedException(); - bool ICollection.Contains(TElement item) => Array.IndexOf(_elements, item, 0, _count) >= 0; void ICollection.CopyTo(TElement[] array, int arrayIndex) => Array.Copy(_elements, 0, array, arrayIndex, _count); - bool ICollection.Remove(TElement item) - { - ThrowModificationNotSupportedException(); - return false; - } - int IList.IndexOf(TElement item) => Array.IndexOf(_elements, item, 0, _count); - void IList.Insert(int index, TElement item) => ThrowModificationNotSupportedException(); - - void IList.RemoveAt(int index) => ThrowModificationNotSupportedException(); - TElement IList.this[int index] { get => index < 0 || index >= _count @@ -274,6 +260,12 @@ TElement IList.this[int index] set => ThrowModificationNotSupportedException(); } + void ICollection.Add(TElement item) => ThrowModificationNotSupportedException(); + void ICollection.Clear() => ThrowModificationNotSupportedException(); + bool ICollection.Remove(TElement item) { ThrowModificationNotSupportedException(); return false; } + void IList.Insert(int index, TElement item) => ThrowModificationNotSupportedException(); + void IList.RemoveAt(int index) => ThrowModificationNotSupportedException(); + [DoesNotReturn] static void ThrowModificationNotSupportedException() => throw new NotSupportedException("Grouping is immutable."); }