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.");
}
}