Skip to content

Commit fdee9c6

Browse files
authored
Merge pull request #3246 from vgromfeld/observableGroupExtensions
Observable group extensions
2 parents 2a9e2e9 + 60af7ba commit fdee9c6

File tree

3 files changed

+839
-0
lines changed

3 files changed

+839
-0
lines changed
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
9+
namespace Microsoft.Toolkit.Collections
10+
{
11+
/// <summary>
12+
/// The extensions methods to simplify the usage of <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
13+
/// </summary>
14+
public static class ObservableGroupedCollectionExtensions
15+
{
16+
/// <summary>
17+
/// Return the first group with <paramref name="key"/> key.
18+
/// </summary>
19+
/// <typeparam name="TKey">The type of the group key.</typeparam>
20+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
21+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
22+
/// <param name="key">The key of the group to query.</param>
23+
/// <returns>The first group matching <paramref name="key"/>.</returns>
24+
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
25+
public static ObservableGroup<TKey, TValue> First<TKey, TValue>(this ObservableGroupedCollection<TKey, TValue> source, TKey key)
26+
=> source.First(group => GroupKeyPredicate(group, key));
27+
28+
/// <summary>
29+
/// Return the first group with <paramref name="key"/> key or null if not found.
30+
/// </summary>
31+
/// <typeparam name="TKey">The type of the group key.</typeparam>
32+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
33+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
34+
/// <param name="key">The key of the group to query.</param>
35+
/// <returns>The first group matching <paramref name="key"/> or null.</returns>
36+
public static ObservableGroup<TKey, TValue> FirstOrDefault<TKey, TValue>(this ObservableGroupedCollection<TKey, TValue> source, TKey key)
37+
=> source.FirstOrDefault(group => GroupKeyPredicate(group, key));
38+
39+
/// <summary>
40+
/// Return the element at position <paramref name="index"/> from the first group with <paramref name="key"/> key.
41+
/// </summary>
42+
/// <typeparam name="TKey">The type of the group key.</typeparam>
43+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
44+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
45+
/// <param name="key">The key of the group to query.</param>
46+
/// <param name="index">The index of the item from the targeted group.</param>
47+
/// <returns>The element.</returns>
48+
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
49+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
50+
public static TValue ElementAt<TKey, TValue>(
51+
this ObservableGroupedCollection<TKey, TValue> source,
52+
TKey key,
53+
int index)
54+
=> source.First(key)[index];
55+
56+
/// <summary>
57+
/// Return the element at position <paramref name="index"/> from the first group with <paramref name="key"/> key.
58+
/// </summary>
59+
/// <typeparam name="TKey">The type of the group key.</typeparam>
60+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
61+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
62+
/// <param name="key">The key of the group to query.</param>
63+
/// <param name="index">The index of the item from the targeted group.</param>
64+
/// <returns>The element or default(TValue) if it does not exist.</returns>
65+
public static TValue ElementAtOrDefault<TKey, TValue>(
66+
this ObservableGroupedCollection<TKey, TValue> source,
67+
TKey key,
68+
int index)
69+
{
70+
var existingGroup = source.FirstOrDefault(key);
71+
if (existingGroup is null)
72+
{
73+
return default;
74+
}
75+
76+
return existingGroup.ElementAtOrDefault(index);
77+
}
78+
79+
/// <summary>
80+
/// Adds a key-value <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
81+
/// </summary>
82+
/// <typeparam name="TKey">The type of the group key.</typeparam>
83+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
84+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
85+
/// <param name="key">The key of the group where <paramref name="value"/> will be added.</param>
86+
/// <param name="value">The value to add.</param>
87+
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
88+
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
89+
this ObservableGroupedCollection<TKey, TValue> source,
90+
TKey key,
91+
TValue value)
92+
=> AddGroup(source, key, new[] { value });
93+
94+
/// <summary>
95+
/// Adds a key-collection <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
96+
/// </summary>
97+
/// <typeparam name="TKey">The type of the group key.</typeparam>
98+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
99+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
100+
/// <param name="key">The key of the group where <paramref name="collection"/> will be added.</param>
101+
/// <param name="collection">The collection to add.</param>
102+
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
103+
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
104+
this ObservableGroupedCollection<TKey, TValue> source,
105+
TKey key,
106+
params TValue[] collection)
107+
=> source.AddGroup(key, (IEnumerable<TValue>)collection);
108+
109+
/// <summary>
110+
/// Adds a key-collection <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
111+
/// </summary>
112+
/// <typeparam name="TKey">The type of the group key.</typeparam>
113+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
114+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
115+
/// <param name="key">The key of the group where <paramref name="collection"/> will be added.</param>
116+
/// <param name="collection">The collection to add.</param>
117+
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
118+
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
119+
this ObservableGroupedCollection<TKey, TValue> source,
120+
TKey key,
121+
IEnumerable<TValue> collection)
122+
{
123+
var group = new ObservableGroup<TKey, TValue>(key, collection);
124+
source.Add(group);
125+
126+
return group;
127+
}
128+
129+
/// <summary>
130+
/// Add <paramref name="item"/> into the first group with <paramref name="key"/> key.
131+
/// If the group does not exist, it will be added.
132+
/// </summary>
133+
/// <typeparam name="TKey">The type of the group key.</typeparam>
134+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
135+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
136+
/// <param name="key">The key of the group where the <paramref name="item"/> should be added.</param>
137+
/// <param name="item">The item to add.</param>
138+
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value. It will either be an existing group or a new group.</returns>
139+
public static ObservableGroup<TKey, TValue> AddItem<TKey, TValue>(
140+
this ObservableGroupedCollection<TKey, TValue> source,
141+
TKey key,
142+
TValue item)
143+
{
144+
var existingGroup = source.FirstOrDefault(key);
145+
if (existingGroup is null)
146+
{
147+
existingGroup = new ObservableGroup<TKey, TValue>(key);
148+
source.Add(existingGroup);
149+
}
150+
151+
existingGroup.Add(item);
152+
return existingGroup;
153+
}
154+
155+
/// <summary>
156+
/// Insert <paramref name="item"/> into the first group with <paramref name="key"/> key at <paramref name="index"/>.
157+
/// </summary>
158+
/// <typeparam name="TKey">The type of the group key.</typeparam>
159+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
160+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
161+
/// <param name="key">The key of the group where to insert <paramref name="item"/>.</param>
162+
/// <param name="index">The index where to insert <paramref name="item"/>.</param>
163+
/// <param name="item">The item to add.</param>
164+
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value.</returns>
165+
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
166+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
167+
public static ObservableGroup<TKey, TValue> InsertItem<TKey, TValue>(
168+
this ObservableGroupedCollection<TKey, TValue> source,
169+
TKey key,
170+
int index,
171+
TValue item)
172+
{
173+
var existingGroup = source.First(key);
174+
existingGroup.Insert(index, item);
175+
return existingGroup;
176+
}
177+
178+
/// <summary>
179+
/// Replace the element at <paramref name="index"/> with <paramref name="item"/> in the first group with <paramref name="key"/> key.
180+
/// </summary>
181+
/// <typeparam name="TKey">The type of the group key.</typeparam>
182+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
183+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
184+
/// <param name="key">The key of the group where to replace the item.</param>
185+
/// <param name="index">The index where to insert <paramref name="item"/>.</param>
186+
/// <param name="item">The item to add.</param>
187+
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value.</returns>
188+
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
189+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
190+
public static ObservableGroup<TKey, TValue> SetItem<TKey, TValue>(
191+
this ObservableGroupedCollection<TKey, TValue> source,
192+
TKey key,
193+
int index,
194+
TValue item)
195+
{
196+
var existingGroup = source.First(key);
197+
existingGroup[index] = item;
198+
return existingGroup;
199+
}
200+
201+
/// <summary>
202+
/// Remove the first occurrence of the group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
203+
/// It will not do anything if the group does not exist.
204+
/// </summary>
205+
/// <typeparam name="TKey">The type of the group key.</typeparam>
206+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
207+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
208+
/// <param name="key">The key of the group to remove.</param>
209+
public static void RemoveGroup<TKey, TValue>(
210+
this ObservableGroupedCollection<TKey, TValue> source,
211+
TKey key)
212+
{
213+
var index = 0;
214+
foreach (var group in source)
215+
{
216+
if (GroupKeyPredicate(group, key))
217+
{
218+
source.RemoveAt(index);
219+
return;
220+
}
221+
222+
index++;
223+
}
224+
}
225+
226+
/// <summary>
227+
/// Remove the first <paramref name="item"/> from the first group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
228+
/// It will not do anything if the group or the item does not exist.
229+
/// </summary>
230+
/// <typeparam name="TKey">The type of the group key.</typeparam>
231+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
232+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
233+
/// <param name="key">The key of the group where the <paramref name="item"/> should be removed.</param>
234+
/// <param name="item">The item to remove.</param>
235+
/// <param name="removeGroupIfEmpty">If true (default value), the group will be removed once it becomes empty.</param>
236+
public static void RemoveItem<TKey, TValue>(
237+
this ObservableGroupedCollection<TKey, TValue> source,
238+
TKey key,
239+
TValue item,
240+
bool removeGroupIfEmpty = true)
241+
{
242+
var index = 0;
243+
foreach (var group in source)
244+
{
245+
if (GroupKeyPredicate(group, key))
246+
{
247+
group.Remove(item);
248+
249+
if (removeGroupIfEmpty && group.Count == 0)
250+
{
251+
source.RemoveAt(index);
252+
}
253+
254+
return;
255+
}
256+
257+
index++;
258+
}
259+
}
260+
261+
/// <summary>
262+
/// Remove the item at <paramref name="index"/> from the first group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
263+
/// It will not do anything if the group or the item does not exist.
264+
/// </summary>
265+
/// <typeparam name="TKey">The type of the group key.</typeparam>
266+
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
267+
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
268+
/// <param name="key">The key of the group where the item at <paramref name="index"/> should be removed.</param>
269+
/// <param name="index">The index of the item to remove in the group.</param>
270+
/// <param name="removeGroupIfEmpty">If true (default value), the group will be removed once it becomes empty.</param>
271+
public static void RemoveItemAt<TKey, TValue>(
272+
this ObservableGroupedCollection<TKey, TValue> source,
273+
TKey key,
274+
int index,
275+
bool removeGroupIfEmpty = true)
276+
{
277+
var groupIndex = 0;
278+
foreach (var group in source)
279+
{
280+
if (GroupKeyPredicate(group, key))
281+
{
282+
group.RemoveAt(index);
283+
284+
if (removeGroupIfEmpty && group.Count == 0)
285+
{
286+
source.RemoveAt(groupIndex);
287+
}
288+
289+
return;
290+
}
291+
292+
groupIndex++;
293+
}
294+
}
295+
296+
private static bool GroupKeyPredicate<TKey, TValue>(ObservableGroup<TKey, TValue> group, TKey expectedKey)
297+
=> EqualityComparer<TKey>.Default.Equals(group.Key, expectedKey);
298+
}
299+
}

0 commit comments

Comments
 (0)