diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 5373f28b7..8ee430a64 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -24,257 +24,249 @@ // 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}")] + internal sealed class Lookup : ILookup { - private 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) + 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)); - Lookup lookup = new Lookup(comparer); - foreach (TSource item in source) { - lookup.GetGrouping(keySelector(item), true).Add(elementSelector(item)); - } + Debug.Assert(source is not null); + Debug.Assert(keySelector is not null); + Debug.Assert(elementSelector is not null); + + var lookup = new Lookup(comparer); + + foreach (var 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) { - Lookup lookup = new Lookup(comparer); - foreach (TElement item in source) { - TKey key = keySelector(item); - if (key != null) lookup.GetGrouping(key, true).Add(item); - } + Debug.Assert(source is not null); + Debug.Assert(keySelector is not null); + + var lookup = new Lookup(comparer); + + foreach (var 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]; + var lookup = new Lookup(comparer); + + foreach (var item in source) + { + if (keySelector(item) is { } key) + lookup.GetGrouping(key, create: true)!.Add(item); + } + + return lookup; } - public int Count + 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(); + var 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) is not null; public IEnumerator> GetEnumerator() { - Grouping g = _lastGrouping; - if (g != null) { - do { - g = g.next; - yield return g; - } while (g != _lastGrouping); - } - } + var g = _lastGrouping; + if (g is not 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 is not null); + yield return g; + } + while (g != _lastGrouping); } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal 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) + 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(); - int index = hashCode % _groupings.Length; - Grouping g = new Grouping(); - g.key = key; - g.hashCode = hashCode; - g.elements = new TElement[1]; - g.hashNext = _groupings[index]; + 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) + { + if (_count == _groupings.Length) + { + Resize(); + } + + var index = hashCode % _groupings.Length; + var g = new Grouping(key, hashCode); + g._hashNext = _groupings[index]; _groupings[index] = g; - if (_lastGrouping == null) { - g.next = g; + if (_lastGrouping is 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() + void Resize() { - 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]; + var newSize = checked((_count * 2) + 1); + var newGroupings = new Grouping[newSize]; + var g = _lastGrouping!; + do + { + g = g._next!; + var 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}")] + internal 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)); - public IEnumerator GetEnumerator() - { - for (int i = 0; i < count; i++) yield return elements[i]; + _elements[_count] = element; + _count++; } - IEnumerator IEnumerable.GetEnumerator() + internal void Trim() { - return GetEnumerator(); + if (_elements.Length != _count) + Array.Resize(ref _elements, _count); } - // DDB195907: implement IGrouping<>.Key implicitly - // so that WPF binding works on this property. - public TKey Key + public IEnumerator GetEnumerator() { - get { return key; } + for (var i = 0; i < _count; i++) + yield return _elements[i]; } - int ICollection.Count - { - get { return count; } - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - bool ICollection.IsReadOnly - { - get { return true; } - } + // DDB195907: implement IGrouping<>.Key implicitly + // so that WPF binding works on this property. + public TKey Key => _key; - void ICollection.Add(TElement item) - { - throw new NotSupportedException("Lookup is immutable"); - } + int ICollection.Count => _count; - void ICollection.Clear() - { - throw new NotSupportedException("Lookup is immutable"); - } + bool ICollection.IsReadOnly => true; - 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"); - } + int IList.IndexOf(TElement item) => Array.IndexOf(_elements, item, 0, _count); - int IList.IndexOf(TElement item) + TElement IList.this[int index] { - return Array.IndexOf(elements, item, 0, count); - } + get => index < 0 || index >= _count + ? throw new ArgumentOutOfRangeException(nameof(index)) + : _elements[index]; - void IList.Insert(int index, TElement item) - { - throw new NotSupportedException("Lookup is immutable"); + set => ThrowModificationNotSupportedException(); } - void IList.RemoveAt(int index) - { - throw new NotSupportedException("Lookup is immutable"); - } + 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(); - TElement IList.this[int index] - { - get - { - if (index < 0 || index >= count) throw new ArgumentOutOfRangeException(nameof(index)); - return elements[index]; - } - set - { - throw new NotSupportedException("Lookup is immutable"); - } - } + [DoesNotReturn] + static void ThrowModificationNotSupportedException() => throw new NotSupportedException("Grouping is immutable."); } }