Skip to content

Commit 0b1cf55

Browse files
authored
refactor: 实现ImmutableObservableCollection解决枚举时集合修改问题 (#384)
1 parent 1f6c281 commit 0b1cf55

File tree

8 files changed

+236
-15
lines changed

8 files changed

+236
-15
lines changed

DownKyi/App.axaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public partial class App : PrismApplication
4343
public const string RepoOwner = "yaobiao131";
4444
public const string RepoName = "downkyicore";
4545

46-
public static ObservableCollection<DownloadingItem> DownloadingList { get; set; } = new();
47-
public static ObservableCollection<DownloadedItem> DownloadedList { get; set; } = new();
46+
public static ImmutableObservableCollection<DownloadingItem> DownloadingList { get; set; } = new();
47+
public static ImmutableObservableCollection<DownloadedItem> DownloadedList { get; set; } = new();
4848
public new static App Current => (App)Application.Current!;
4949
public new MainWindow MainWindow => Container.Resolve<MainWindow>();
5050
public IClassicDesktopStyleApplicationLifetime? AppLife;

DownKyi/Services/Download/AriaDownloadService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
using DownKyi.Models;
1818
using DownKyi.PrismExtension.Dialog;
1919
using DownKyi.Utils;
20+
using DownKyi.ViewModels;
2021
using DownKyi.ViewModels.DownloadManager;
2122

2223
namespace DownKyi.Services.Download;
2324

2425
public class AriaDownloadService : DownloadService, IDownloadService
2526
{
2627
public AriaDownloadService(
27-
ObservableCollection<DownloadingItem> downloadingList,
28-
ObservableCollection<DownloadedItem> downloadedList,
28+
ImmutableObservableCollection<DownloadingItem> downloadingList,
29+
ImmutableObservableCollection<DownloadedItem> downloadedList,
2930
IDialogService? dialogService) :
3031
base(downloadingList, downloadedList, dialogService)
3132
{

DownKyi/Services/Download/BuiltinDownloadService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using DownKyi.Models;
1414
using DownKyi.PrismExtension.Dialog;
1515
using DownKyi.Utils;
16+
using DownKyi.ViewModels;
1617
using DownKyi.ViewModels.DownloadManager;
1718
using Downloader;
1819
using Microsoft.Extensions.Logging;
@@ -24,8 +25,8 @@ namespace DownKyi.Services.Download;
2425

2526
public class BuiltinDownloadService : DownloadService, IDownloadService
2627
{
27-
public BuiltinDownloadService(ObservableCollection<DownloadingItem> downloadingList,
28-
ObservableCollection<DownloadedItem> downloadedList,
28+
public BuiltinDownloadService(ImmutableObservableCollection<DownloadingItem> downloadingList,
29+
ImmutableObservableCollection<DownloadedItem> downloadedList,
2930
IDialogService? dialogService
3031
) : base(downloadingList, downloadedList, dialogService)
3132
{

DownKyi/Services/Download/CustomAriaDownloadService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using DownKyi.Utils;
1717
using DownKyi.ViewModels.DownloadManager;
1818
using DownKyi.PrismExtension.Dialog;
19+
using DownKyi.ViewModels;
1920

2021
namespace DownKyi.Services.Download;
2122

@@ -24,8 +25,8 @@ namespace DownKyi.Services.Download;
2425
/// </summary>
2526
public class CustomAriaDownloadService : DownloadService, IDownloadService
2627
{
27-
public CustomAriaDownloadService(ObservableCollection<DownloadingItem> downloadingList,
28-
ObservableCollection<DownloadedItem> downloadedList,
28+
public CustomAriaDownloadService(ImmutableObservableCollection<DownloadingItem> downloadingList,
29+
ImmutableObservableCollection<DownloadedItem> downloadedList,
2930
IDialogService? dialogService
3031
) : base(downloadingList, downloadedList, dialogService)
3132
{

DownKyi/Services/Download/DownloadService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using DownKyi.Models;
2222
using DownKyi.PrismExtension.Dialog;
2323
using DownKyi.Utils;
24+
using DownKyi.ViewModels;
2425
using DownKyi.ViewModels.DownloadManager;
2526
using Console = DownKyi.Core.Utils.Debugging.Console;
2627

@@ -32,8 +33,8 @@ public abstract class DownloadService
3233

3334
// protected TaskbarIcon _notifyIcon;
3435
protected readonly IDialogService? DialogService;
35-
protected readonly ObservableCollection<DownloadingItem> DownloadingList;
36-
protected readonly ObservableCollection<DownloadedItem> DownloadedList;
36+
protected readonly ImmutableObservableCollection<DownloadingItem> DownloadingList;
37+
protected readonly ImmutableObservableCollection<DownloadedItem> DownloadedList;
3738

3839
protected Task? WorkTask;
3940
protected CancellationTokenSource? TokenSource;
@@ -52,7 +53,7 @@ public abstract class DownloadService
5253
/// <param name="downloadedList"></param>
5354
/// <param name="dialogService"></param>
5455
/// <returns></returns>
55-
public DownloadService(ObservableCollection<DownloadingItem> downloadingList, ObservableCollection<DownloadedItem> downloadedList, IDialogService? dialogService)
56+
public DownloadService(ImmutableObservableCollection<DownloadingItem> downloadingList, ImmutableObservableCollection<DownloadedItem> downloadedList, IDialogService? dialogService)
5657
{
5758
DownloadingList = downloadingList;
5859
DownloadedList = downloadedList;

DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ public class ViewDownloadFinishedViewModel : ViewModelBase
2525

2626
#region 页面属性申明
2727

28-
private ObservableCollection<DownloadedItem> _downloadedList = new();
28+
private ImmutableObservableCollection<DownloadedItem> _downloadedList = new();
2929

30-
public ObservableCollection<DownloadedItem> DownloadedList
30+
public ImmutableObservableCollection<DownloadedItem> DownloadedList
3131
{
3232
get => _downloadedList;
3333
set => SetProperty(ref _downloadedList, value);

DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public class ViewDownloadingViewModel : ViewModelBase
2121

2222
#region 页面属性申明
2323

24-
private ObservableCollection<DownloadingItem> _downloadingList = new();
24+
private ImmutableObservableCollection<DownloadingItem> _downloadingList = new();
2525

26-
public ObservableCollection<DownloadingItem> DownloadingList
26+
public ImmutableObservableCollection<DownloadingItem> DownloadingList
2727
{
2828
get => _downloadingList;
2929
set => SetProperty(ref _downloadingList, value);
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Collections.Immutable;
5+
using System.Collections.Specialized;
6+
using System.ComponentModel;
7+
using System.Runtime.CompilerServices;
8+
namespace DownKyi.ViewModels;
9+
10+
public sealed class ImmutableObservableCollection<T> : IList<T>,IList, INotifyCollectionChanged, INotifyPropertyChanged
11+
{
12+
private ImmutableList<T> _items;
13+
14+
public ImmutableObservableCollection()
15+
{
16+
_items = ImmutableList<T>.Empty;
17+
}
18+
19+
public ImmutableObservableCollection(IEnumerable<T> items)
20+
{
21+
_items = ImmutableList<T>.Empty.AddRange(items);
22+
}
23+
24+
public T this[int index]
25+
{
26+
get => _items[index];
27+
set
28+
{
29+
var oldItem = _items[index];
30+
_items = _items.SetItem(index, value);
31+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
32+
NotifyCollectionChangedAction.Replace, value, oldItem, index));
33+
}
34+
}
35+
36+
public int IndexOf(T item) => _items.IndexOf(item);
37+
38+
public void Insert(int index, T item)
39+
{
40+
_items = _items.Insert(index, item);
41+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
42+
NotifyCollectionChangedAction.Add, item, index));
43+
OnPropertyChanged(nameof(Count));
44+
}
45+
46+
public void Remove(object? value)
47+
{
48+
throw new NotImplementedException();
49+
}
50+
51+
public void RemoveAt(int index)
52+
{
53+
var removedItem = _items[index];
54+
_items = _items.RemoveAt(index);
55+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
56+
NotifyCollectionChangedAction.Remove, removedItem, index));
57+
OnPropertyChanged(nameof(Count));
58+
}
59+
60+
public bool IsFixedSize => false;
61+
62+
public void Add(T item)
63+
{
64+
CheckReentrancy();
65+
_items = _items.Add(item);
66+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
67+
NotifyCollectionChangedAction.Add, item, _items.Count - 1));
68+
OnPropertyChanged(nameof(Count));
69+
}
70+
71+
public bool Remove(T item)
72+
{
73+
CheckReentrancy();
74+
var index = _items.IndexOf(item);
75+
if (index < 0) return false;
76+
77+
_items = _items.Remove(item);
78+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
79+
NotifyCollectionChangedAction.Remove, item, index));
80+
OnPropertyChanged(nameof(Count));
81+
return true;
82+
}
83+
84+
public int Add(object? value)
85+
{
86+
#nullable disable
87+
T obj;
88+
try
89+
{
90+
obj = (T) value;
91+
}
92+
catch (InvalidCastException ex)
93+
{
94+
throw new ArgumentException(
95+
$"Value cannot be cast to type {typeof(T).Name}.",
96+
nameof(value), ex);
97+
}
98+
this.Add(obj);
99+
return this.Count - 1;
100+
}
101+
102+
#nullable restore
103+
104+
public void Clear()
105+
{
106+
if (_items.IsEmpty) return;
107+
CheckReentrancy();
108+
_items = ImmutableList<T>.Empty;
109+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
110+
NotifyCollectionChangedAction.Reset));
111+
OnPropertyChanged(nameof(Count));
112+
}
113+
114+
public bool Contains(object? value)
115+
{
116+
throw new NotImplementedException();
117+
}
118+
119+
public int IndexOf(object? value)
120+
{
121+
throw new NotImplementedException();
122+
}
123+
124+
public void Insert(int index, object? value)
125+
{
126+
throw new NotImplementedException();
127+
}
128+
129+
public bool Contains(T item) => _items.Contains(item);
130+
public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
131+
public void CopyTo(Array array, int index)
132+
{
133+
throw new NotImplementedException();
134+
}
135+
136+
public int Count => _items.Count;
137+
public bool IsSynchronized => false;
138+
public object SyncRoot => this;
139+
public bool IsReadOnly => false;
140+
object? IList.this[int index]
141+
{
142+
get => this[index];
143+
set
144+
{
145+
#nullable disable
146+
T obj;
147+
try
148+
{
149+
obj = (T) value;
150+
}
151+
catch (InvalidCastException ex)
152+
{
153+
throw new ArgumentException(
154+
$"Value cannot be cast to type {typeof(T).Name}.",
155+
nameof(value), ex);
156+
}
157+
this[index] = obj;
158+
#nullable restore
159+
}
160+
}
161+
162+
163+
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
164+
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
165+
166+
public event NotifyCollectionChangedEventHandler? CollectionChanged;
167+
public event PropertyChangedEventHandler? PropertyChanged;
168+
169+
private int _blockReentrancyCount;
170+
171+
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
172+
{
173+
NotifyCollectionChangedEventHandler? collectionChanged = this.CollectionChanged;
174+
if (collectionChanged == null)
175+
return;
176+
177+
++this._blockReentrancyCount;
178+
try
179+
{
180+
collectionChanged(this, e);
181+
}
182+
finally
183+
{
184+
--this._blockReentrancyCount;
185+
}
186+
}
187+
188+
189+
private void CheckReentrancy()
190+
{
191+
if (this._blockReentrancyCount <= 0)
192+
return;
193+
NotifyCollectionChangedEventHandler? collectionChanged = this.CollectionChanged;
194+
if (collectionChanged != null && !HasSingleTarget(collectionChanged))
195+
{
196+
throw new InvalidOperationException("ObservableCollectionReentrancyNotAllowed");
197+
}
198+
}
199+
200+
private bool HasSingleTarget(NotifyCollectionChangedEventHandler? handler)
201+
{
202+
if (handler == null)
203+
return true;
204+
205+
return handler.GetInvocationList().Length <= 1;
206+
}
207+
208+
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
209+
{
210+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
211+
}
212+
213+
public void AddRange(List<T> downloadingItems)
214+
{
215+
_items = _items.AddRange(downloadingItems);
216+
}
217+
}

0 commit comments

Comments
 (0)