Skip to content

Commit 1cc25d0

Browse files
grokysdanwalmsley
authored andcommitted
Merge pull request #6485 from AvaloniaUI/fixes/itemssourceview-fixes
Tweaks to ItemsSourceView
1 parent d12d992 commit 1cc25d0

File tree

2 files changed

+124
-37
lines changed

2 files changed

+124
-37
lines changed

src/Avalonia.Controls/ItemsSourceView.cs

+61-37
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public class ItemsSourceView : INotifyCollectionChanged, IDisposable
3232
/// </summary>
3333
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
3434

35-
private protected readonly IList _inner;
36-
private INotifyCollectionChanged? _notifyCollectionChanged;
35+
private IList? _inner;
36+
private NotifyCollectionChangedEventHandler? _collectionChanged;
3737

3838
/// <summary>
3939
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
@@ -42,27 +42,22 @@ public class ItemsSourceView : INotifyCollectionChanged, IDisposable
4242
public ItemsSourceView(IEnumerable source)
4343
{
4444
source = source ?? throw new ArgumentNullException(nameof(source));
45-
46-
if (source is IList list)
47-
{
48-
_inner = list;
49-
}
50-
else if (source is IEnumerable<object> objectEnumerable)
45+
_inner = source switch
5146
{
52-
_inner = new List<object>(objectEnumerable);
53-
}
54-
else
55-
{
56-
_inner = new List<object>(source.Cast<object>());
57-
}
58-
59-
ListenToCollectionChanges();
47+
ItemsSourceView _ => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
48+
IList list => list,
49+
INotifyCollectionChanged _ => throw new ArgumentException(
50+
"Collection implements INotifyCollectionChanged by not IList.",
51+
nameof(source)),
52+
IEnumerable<object> iObj => new List<object>(iObj),
53+
_ => new List<object>(source.Cast<object>())
54+
};
6055
}
6156

6257
/// <summary>
6358
/// Gets the number of items in the collection.
6459
/// </summary>
65-
public int Count => _inner.Count;
60+
public int Count => Inner.Count;
6661

6762
/// <summary>
6863
/// Gets a value that indicates whether the items source can provide a unique key for each item.
@@ -72,6 +67,19 @@ public ItemsSourceView(IEnumerable source)
7267
/// </remarks>
7368
public bool HasKeyIndexMapping => false;
7469

70+
/// <summary>
71+
/// Gets the inner collection.
72+
/// </summary>
73+
public IList Inner
74+
{
75+
get
76+
{
77+
if (_inner is null)
78+
ThrowDisposed();
79+
return _inner!;
80+
}
81+
}
82+
7583
/// <summary>
7684
/// Retrieves the item at the specified index.
7785
/// </summary>
@@ -82,25 +90,48 @@ public ItemsSourceView(IEnumerable source)
8290
/// <summary>
8391
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
8492
/// </summary>
85-
public event NotifyCollectionChangedEventHandler? CollectionChanged;
93+
public event NotifyCollectionChangedEventHandler? CollectionChanged
94+
{
95+
add
96+
{
97+
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
98+
{
99+
incc.CollectionChanged += OnCollectionChanged;
100+
}
101+
102+
_collectionChanged += value;
103+
}
104+
105+
remove
106+
{
107+
_collectionChanged -= value;
108+
109+
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
110+
{
111+
incc.CollectionChanged -= OnCollectionChanged;
112+
}
113+
}
114+
}
86115

87116
/// <inheritdoc/>
88117
public void Dispose()
89118
{
90-
if (_notifyCollectionChanged != null)
119+
if (_inner is INotifyCollectionChanged incc)
91120
{
92-
_notifyCollectionChanged.CollectionChanged -= OnCollectionChanged;
121+
incc.CollectionChanged -= OnCollectionChanged;
93122
}
123+
124+
_inner = null;
94125
}
95126

96127
/// <summary>
97128
/// Retrieves the item at the specified index.
98129
/// </summary>
99130
/// <param name="index">The index.</param>
100131
/// <returns>The item.</returns>
101-
public object? GetAt(int index) => _inner[index];
132+
public object? GetAt(int index) => Inner[index];
102133

103-
public int IndexOf(object? item) => _inner.IndexOf(item);
134+
public int IndexOf(object? item) => Inner.IndexOf(item);
104135

105136
public static ItemsSourceView GetOrCreate(IEnumerable? items)
106137
{
@@ -146,38 +177,31 @@ public int IndexFromKey(string key)
146177

147178
internal void AddListener(ICollectionChangedListener listener)
148179
{
149-
if (_inner is INotifyCollectionChanged incc)
180+
if (Inner is INotifyCollectionChanged incc)
150181
{
151182
CollectionChangedEventManager.Instance.AddListener(incc, listener);
152183
}
153184
}
154185

155186
internal void RemoveListener(ICollectionChangedListener listener)
156187
{
157-
if (_inner is INotifyCollectionChanged incc)
188+
if (Inner is INotifyCollectionChanged incc)
158189
{
159190
CollectionChangedEventManager.Instance.RemoveListener(incc, listener);
160191
}
161192
}
162193

163194
protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args)
164195
{
165-
CollectionChanged?.Invoke(this, args);
166-
}
167-
168-
private void ListenToCollectionChanges()
169-
{
170-
if (_inner is INotifyCollectionChanged incc)
171-
{
172-
incc.CollectionChanged += OnCollectionChanged;
173-
_notifyCollectionChanged = incc;
174-
}
196+
_collectionChanged?.Invoke(this, args);
175197
}
176198

177199
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
178200
{
179201
OnItemsSourceChanged(e);
180202
}
203+
204+
private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView));
181205
}
182206

183207
public class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
@@ -216,10 +240,10 @@ private ItemsSourceView(IEnumerable source)
216240
/// <param name="index">The index.</param>
217241
/// <returns>The item.</returns>
218242
[return: MaybeNull]
219-
public new T GetAt(int index) => (T)_inner[index];
243+
public new T GetAt(int index) => (T)Inner[index];
220244

221-
public IEnumerator<T> GetEnumerator() => _inner.Cast<T>().GetEnumerator();
222-
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
245+
public IEnumerator<T> GetEnumerator() => Inner.Cast<T>().GetEnumerator();
246+
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
223247

224248
public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
225249
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Collections.Specialized;
5+
using System.Text;
6+
using Avalonia.Collections;
7+
using Avalonia.Diagnostics;
8+
using Xunit;
9+
10+
namespace Avalonia.Controls.UnitTests
11+
{
12+
public class ItemsSourceViewTests
13+
{
14+
[Fact]
15+
public void Only_Subscribes_To_Source_CollectionChanged_When_CollectionChanged_Subscribed()
16+
{
17+
var source = new AvaloniaList<string>();
18+
var target = new ItemsSourceView<string>(source);
19+
var debug = (INotifyCollectionChangedDebug)source;
20+
21+
Assert.Null(debug.GetCollectionChangedSubscribers());
22+
23+
void Handler(object sender, NotifyCollectionChangedEventArgs e) { }
24+
target.CollectionChanged += Handler;
25+
26+
Assert.NotNull(debug.GetCollectionChangedSubscribers());
27+
Assert.Equal(1, debug.GetCollectionChangedSubscribers().Length);
28+
29+
target.CollectionChanged -= Handler;
30+
31+
Assert.Null(debug.GetCollectionChangedSubscribers());
32+
}
33+
34+
[Fact]
35+
public void Cannot_Wrap_An_ItemsSourceView_In_Another()
36+
{
37+
var source = new ItemsSourceView<string>(new string[0]);
38+
Assert.Throws<ArgumentException>(() => new ItemsSourceView<string>(source));
39+
}
40+
41+
[Fact]
42+
public void Cannot_Create_ItemsSourceView_With_Collection_That_Implements_INCC_But_Not_List()
43+
{
44+
var source = new InvalidCollection();
45+
Assert.Throws<ArgumentException>(() => new ItemsSourceView<string>(source));
46+
}
47+
48+
private class InvalidCollection : INotifyCollectionChanged, IEnumerable<string>
49+
{
50+
public event NotifyCollectionChangedEventHandler CollectionChanged;
51+
52+
public IEnumerator<string> GetEnumerator()
53+
{
54+
yield break;
55+
}
56+
57+
IEnumerator IEnumerable.GetEnumerator()
58+
{
59+
yield break;
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)