Skip to content

Commit bf31152

Browse files
committed
Optimize ILookup<TKey, TElement> implementations
1 parent f6a0f3f commit bf31152

File tree

3 files changed

+58
-18
lines changed

3 files changed

+58
-18
lines changed

CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace CommunityToolkit.Mvvm.Collections;
1717
public static class ObservableGroupedCollectionExtensions
1818
{
1919
/// <summary>
20-
/// Return the first group with <paramref name="key"/> key.
20+
/// Returns the first group with <paramref name="key"/> key.
2121
/// </summary>
2222
/// <typeparam name="TKey">The type of the group key.</typeparam>
2323
/// <typeparam name="TElement">The type of the items in the collection.</typeparam>
@@ -49,13 +49,13 @@ static void ThrowArgumentExceptionForKeyNotFound()
4949
}
5050

5151
/// <summary>
52-
/// Return the first group with <paramref name="key"/> key or null if not found.
52+
/// Returns the first group with <paramref name="key"/> key or <see langword="null"/> if not found.
5353
/// </summary>
5454
/// <typeparam name="TKey">The type of the group key.</typeparam>
5555
/// <typeparam name="TElement">The type of the items in the collection.</typeparam>
5656
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TElement}"/> instance.</param>
5757
/// <param name="key">The key of the group to query.</param>
58-
/// <returns>The first group matching <paramref name="key"/> or null.</returns>
58+
/// <returns>The first group matching <paramref name="key"/> or <see langword="null"/>.</returns>
5959
/// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> or <paramref name="key"/> are <see langword="null"/>.</exception>
6060
public static ObservableGroup<TKey, TElement>? FirstGroupByKeyOrDefault<TKey, TElement>(this ObservableGroupedCollection<TKey, TElement> source, TKey key)
6161
where TKey : notnull
@@ -65,7 +65,7 @@ static void ThrowArgumentExceptionForKeyNotFound()
6565

6666
if (source.TryGetList(out List<ObservableGroup<TKey, TElement>>? list))
6767
{
68-
foreach (ObservableGroup<TKey, TElement>? group in list)
68+
foreach (ObservableGroup<TKey, TElement> group in list)
6969
{
7070
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
7171
{
@@ -77,12 +77,12 @@ static void ThrowArgumentExceptionForKeyNotFound()
7777
}
7878

7979
[MethodImpl(MethodImplOptions.NoInlining)]
80-
static ObservableGroup<TKey, TElement>? FirstOrDefaultFallback(ObservableGroupedCollection<TKey, TElement> source, TKey key)
80+
static ObservableGroup<TKey, TElement>? FirstGroupByKeyOrDefaultFallback(ObservableGroupedCollection<TKey, TElement> source, TKey key)
8181
{
8282
return Enumerable.FirstOrDefault<ObservableGroup<TKey, TElement>>(source, group => EqualityComparer<TKey>.Default.Equals(group.Key, key));
8383
}
8484

85-
return FirstOrDefaultFallback(source, key);
85+
return FirstGroupByKeyOrDefaultFallback(source, key);
8686
}
8787

8888
/// <summary>
@@ -720,7 +720,7 @@ public static void RemoveGroup<TKey, TValue>(this ObservableGroupedCollection<TK
720720
{
721721
int index = 0;
722722

723-
foreach (ObservableGroup<TKey, TValue>? group in list)
723+
foreach (ObservableGroup<TKey, TValue> group in list)
724724
{
725725
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
726726
{
@@ -739,7 +739,7 @@ static void RemoveGroupFallback(ObservableGroupedCollection<TKey, TValue> source
739739
{
740740
int index = 0;
741741

742-
foreach (ObservableGroup<TKey, TValue>? group in source)
742+
foreach (ObservableGroup<TKey, TValue> group in source)
743743
{
744744
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
745745
{
@@ -776,7 +776,7 @@ public static void RemoveItem<TKey, TValue>(this ObservableGroupedCollection<TKe
776776
{
777777
int index = 0;
778778

779-
foreach (ObservableGroup<TKey, TValue>? group in list)
779+
foreach (ObservableGroup<TKey, TValue> group in list)
780780
{
781781
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
782782
{
@@ -800,7 +800,7 @@ static void RemoveItemFallback(ObservableGroupedCollection<TKey, TValue> source,
800800
{
801801
int index = 0;
802802

803-
foreach (ObservableGroup<TKey, TValue>? group in source)
803+
foreach (ObservableGroup<TKey, TValue> group in source)
804804
{
805805
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
806806
{

CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,14 @@ IEnumerable<TElement> ILookup<TKey, TElement>.this[TKey key]
4040
{
4141
get
4242
{
43-
// TODO: optimize this
44-
return Enumerable.FirstOrDefault<ObservableGroup<TKey, TElement>>(this, item => EqualityComparer<TKey>.Default.Equals(item.Key, key)) ?? Enumerable.Empty<TElement>();
43+
IEnumerable<TElement>? result = null;
44+
45+
if (key is not null)
46+
{
47+
result = this.FirstGroupByKeyOrDefault(key);
48+
}
49+
50+
return result ?? Enumerable.Empty<TElement>();
4551
}
4652
}
4753

@@ -61,8 +67,7 @@ internal bool TryGetList([NotNullWhen(true)] out List<ObservableGroup<TKey, TEle
6167
/// <inheritdoc/>
6268
bool ILookup<TKey, TElement>.Contains(TKey key)
6369
{
64-
// TODO: optimize this
65-
return Enumerable.Any<ObservableGroup<TKey, TElement>>(this, item => EqualityComparer<TKey>.Default.Equals(item.Key, key));
70+
return key is not null && this.FirstGroupByKey(key) is not null;
6671
}
6772

6873
/// <inheritdoc/>

CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Specialized;
99
using System.Diagnostics.CodeAnalysis;
1010
using System.Linq;
11+
using System.Runtime.CompilerServices;
1112

1213
namespace CommunityToolkit.Mvvm.Collections;
1314

@@ -45,16 +46,21 @@ IEnumerable<TElement> ILookup<TKey, TElement>.this[TKey key]
4546
{
4647
get
4748
{
48-
// TODO: optimize this
49-
return Enumerable.FirstOrDefault<ReadOnlyObservableGroup<TKey, TElement>>(this, item => EqualityComparer<TKey>.Default.Equals(item.Key, key)) ?? Enumerable.Empty<TElement>();
49+
IEnumerable<TElement>? result = null;
50+
51+
if (key is not null)
52+
{
53+
result = FirstGroupByKeyOrDefault(key);
54+
}
55+
56+
return result ?? Enumerable.Empty<TElement>();
5057
}
5158
}
5259

5360
/// <inheritdoc/>
5461
bool ILookup<TKey, TElement>.Contains(TKey key)
5562
{
56-
// TODO: optimize this
57-
return Enumerable.Any<ReadOnlyObservableGroup<TKey, TElement>>(this, item => EqualityComparer<TKey>.Default.Equals(item.Key, key));
63+
return key is not null && FirstGroupByKeyOrDefault(key) is not null;
5864
}
5965

6066
/// <inheritdoc/>
@@ -153,4 +159,33 @@ static void ThrowNotSupportedExceptionForRangeOperation()
153159
break;
154160
}
155161
}
162+
163+
/// <summary>
164+
/// Returns the first group with <paramref name="key"/> key or <see langword="null"/> if not found.
165+
/// </summary>
166+
/// <param name="key">The key of the group to query (assumed not to be <see langword="null"/>).</param>
167+
/// <returns>The first group matching <paramref name="key"/>.</returns>
168+
private IEnumerable<TElement>? FirstGroupByKeyOrDefault(TKey key)
169+
{
170+
if (Items is List<ReadOnlyObservableGroup<TKey, TElement>> list)
171+
{
172+
foreach (ReadOnlyObservableGroup<TKey, TElement> group in list)
173+
{
174+
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
175+
{
176+
return group;
177+
}
178+
}
179+
180+
return null;
181+
}
182+
183+
[MethodImpl(MethodImplOptions.NoInlining)]
184+
static IEnumerable<TElement>? FirstGroupByKeyOrDefaultFallback(ReadOnlyObservableGroupedCollection<TKey, TElement> source, TKey key)
185+
{
186+
return Enumerable.FirstOrDefault<ReadOnlyObservableGroup<TKey, TElement>>(source, group => EqualityComparer<TKey>.Default.Equals(group.Key, key));
187+
}
188+
189+
return FirstGroupByKeyOrDefaultFallback(this, key);
190+
}
156191
}

0 commit comments

Comments
 (0)