Remove INotifyCollectionChanged implementation.
Resolve
Resolve
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
@@ -38,7 +38,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
Password = "";
- var onCoinsSetModified = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.CollectionChanged))
+ var onCoinsSetModified = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.HashSetChanged))
.ObserveOn(RxApp.MainThreadScheduler);
var globalCoins = Global.WalletService.Coins.CreateDerivedCollection(c => new CoinViewModel(c), null, (first, second) => second.Amount.CompareTo(first.Amount), signalReset: onCoinsSetModified, RxApp.MainThreadScheduler);

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs
@@ -33,7 +33,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Transactions = new ObservableCollection<TransactionViewModel>();
RewriteTable();
- var coinsChanged = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.CollectionChanged));
+ var coinsChanged = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.HashSetChanged));
var newBlockProcessed = Observable.FromEventPattern(Global.WalletService, nameof(Global.WalletService.NewBlockProcessed));
var coinSpent = Observable.FromEventPattern(Global.WalletService, nameof(Global.WalletService.CoinSpentOrSpenderConfirmed));

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
@@ -28,7 +28,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
_addresses = new ObservableCollection<AddressViewModel>();
- Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.CollectionChanged))
+ Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.HashSetChanged))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(o =>
{

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
@@ -37,7 +37,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public SendTabViewModel(WalletViewModel walletViewModel)
: base("Send", walletViewModel)
{
- var onCoinsSetModified = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.CollectionChanged))
+ var onCoinsSetModified = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.HashSetChanged))
.ObserveOn(RxApp.MainThreadScheduler);
var globalCoins = Global.WalletService.Coins.CreateDerivedCollection(c => new CoinViewModel(c), null, (first, second) => second.Amount.CompareTo(first.Amount), signalReset: onCoinsSetModified, RxApp.MainThreadScheduler);

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
@@ -27,7 +27,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public WalletViewModel(string name, bool receiveDominant)
: base(name)
{
- var coinsChanged = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.CollectionChanged));
+ var coinsChanged = Observable.FromEventPattern(Global.WalletService.Coins, nameof(Global.WalletService.Coins.HashSetChanged));
var coinSpent = Observable.FromEventPattern(Global.WalletService, nameof(Global.WalletService.CoinSpentOrSpenderConfirmed));
coinsChanged

MODIFY
WalletWasabi.Tests/ModelTests.cs
WalletWasabi.Tests/ModelTests.cs
@@ -157,96 +157,82 @@ namespace WalletWasabi.Tests
{
var set = new ObservableConcurrentHashSet<int>();
- set.CollectionChanged += Set_CollectionChanged;
+ set.HashSetChanged += Set_HashSetChanged;
try
{
- // CollectionChanged fire 1
+ // HashSetChanged fire 1
set.TryAdd(1);
Assert.Contains(1, set);
Assert.Single(set);
- // CollectionChanged don't fire
+ // HashSetChanged don't fire
set.TryAdd(1);
Assert.Contains(1, set);
Assert.Single(set);
- // CollectionChanged don't fire
+ // HashSetChanged don't fire
set.TryRemove(2);
Assert.Single(set);
- // CollectionChanged fire 2
+ // HashSetChanged fire 2
set.TryAdd(2);
Assert.Contains(2, set);
Assert.Equal(2, set.Count);
- // CollectionChanged fire 3
+ // HashSetChanged fire 3
set.TryRemove(2);
Assert.Contains(1, set);
Assert.DoesNotContain(2, set);
Assert.Single(set);
- // CollectionChanged fire 4
+ // HashSetChanged fire 4
set.TryAdd(3);
Assert.Contains(1, set);
Assert.Contains(3, set);
Assert.Equal(2, set.Count);
- // CollectionChanged fire 5
+ // HashSetChanged fire 5
set.Clear();
Assert.NotNull(set);
Assert.Empty(set);
}
finally
{
- set.CollectionChanged -= Set_CollectionChanged;
+ set.HashSetChanged -= Set_HashSetChanged;
}
}
- private long _set_CollectionChanged_InvokeCount = 0;
+ private long _set_HashSetChanged_InvokeCount = 0;
- private void Set_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ private void Set_HashSetChanged(object sender, EventArgs e)
{
- Interlocked.Increment(ref _set_CollectionChanged_InvokeCount);
+ Interlocked.Increment(ref _set_HashSetChanged_InvokeCount);
- switch (Interlocked.Read(ref _set_CollectionChanged_InvokeCount))
+ switch (Interlocked.Read(ref _set_HashSetChanged_InvokeCount))
{
case 1:
{
- Assert.Equal(NotifyCollectionChangedAction.Add, e.Action);
- Assert.Single(e.NewItems);
- Assert.Null(e.OldItems);
- Assert.Equal(1, e.NewItems[0]);
+ Assert.Equal(EventArgs.Empty, e);
break;
}
case 2:
{
- Assert.Equal(NotifyCollectionChangedAction.Add, e.Action);
- Assert.Single(e.NewItems);
- Assert.Null(e.OldItems);
- Assert.Equal(2, e.NewItems[0]);
+ Assert.Equal(EventArgs.Empty, e);
break;
}
case 3:
{
- Assert.Equal(NotifyCollectionChangedAction.Remove, e.Action);
- Assert.Null(e.NewItems);
- Assert.Single(e.OldItems);
- Assert.Equal(2, e.OldItems[0]);
+ Assert.Equal(EventArgs.Empty, e);
break;
}
case 4:
{
- Assert.Equal(NotifyCollectionChangedAction.Add, e.Action);
- Assert.Single(e.NewItems);
- Assert.Null(e.OldItems);
- Assert.Equal(3, e.NewItems[0]);
+ Assert.Equal(EventArgs.Empty, e);
break;
}
case 5:
{
- Assert.Equal(NotifyCollectionChangedAction.Reset, e.Action);
- Assert.Null(e.NewItems);
- Assert.Null(e.OldItems); // "Reset action must be initialized with no changed items."
+ Assert.Equal(EventArgs.Empty, e);
break;
}
default: throw new NotSupportedException();

MODIFY
WalletWasabi/Models/ObservableConcurrentHashSet.cs
WalletWasabi/Models/ObservableConcurrentHashSet.cs
@@ -8,12 +8,12 @@ using System.Text;
namespace WalletWasabi.Models
{
- public class ObservableConcurrentHashSet<T> : IReadOnlyCollection<T>, INotifyCollectionChanged
+ public class ObservableConcurrentHashSet<T> : IReadOnlyCollection<T> // DO NOT IMPLEMENT INotifyCollectionChanged!!! That'll break and crash the software: https://github.com/AvaloniaUI/Avalonia/issues/1988#issuecomment-431691863
{
private ConcurrentHashSet<T> Set { get; }
private object Lock { get; }
- public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event EventHandler HashSetChanged; // Keep it as is! Unless with the modification this bug won't come out: https://github.com/AvaloniaUI/Avalonia/issues/1988#issuecomment-431691863
public ObservableConcurrentHashSet()
{
@@ -48,7 +48,7 @@ namespace WalletWasabi.Models
{
if (Set.Add(item))
{
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
+ HashSetChanged?.Invoke(this, EventArgs.Empty);
return true;
}
return false;
@@ -61,7 +61,7 @@ namespace WalletWasabi.Models
{
if (Set.TryRemove(item))
{
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
+ HashSetChanged?.Invoke(this, EventArgs.Empty);
return true;
}
return false;
@@ -75,8 +75,7 @@ namespace WalletWasabi.Models
if (Set.Count > 0)
{
Set.Clear();
- // "Reset action must be initialized with no changed items."
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ HashSetChanged?.Invoke(this, EventArgs.Empty);
}
}
}

Remove INotifyCollectionChanged implementation.
Resolve https://github.com/zkSNACKs/WalletWasabi/issues/750
Resolve https://github.com/AvaloniaUI/Avalonia/issues/1988