Dispose properly and clearly.
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
@@ -264,6 +264,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
.Sort(MyComparer, comparerChanged: sortChanged, resetThreshold: 5)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _coinViewModels)
+ .OnItemRemoved(cvm => cvm.Dispose())
.Subscribe();
SortCommand = ReactiveCommand.Create(RefreshOrdering);
@@ -414,16 +415,14 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private void OnOpen()
{
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
+ Disposables = Disposables is null ?
+ new CompositeDisposable() :
+ throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
var list = Global.WalletService.Coins.Select(x => new CoinViewModel(this, x)).ToList();
- foreach (var vm in list)
- {
- vm.SubscribeEvents();
- }
-
RootList.AddRange(list);
+ RootList.DisposeWith(Disposables);
Global.UiConfig
.WhenAnyValue(x => x.LurkingWifeMode)
@@ -441,9 +440,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
try
{
- var newCoinVm = new CoinViewModel(this, coin.EventArgs);
- newCoinVm.SubscribeEvents();
- RootList.Add(newCoinVm);
+ RootList.Add(new CoinViewModel(this, coin.EventArgs));
}
catch (Exception ex)
{
@@ -463,7 +460,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
toRemove.IsSelected = false;
RootList.Remove(toRemove);
- toRemove.UnsubscribeEvents();
}
}
catch (Exception ex)
@@ -480,10 +476,13 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
try
{
var toRemove = args.EventArgs.DestroyedCoins;
- RemoveCoins(RootList.Items.Where(cvm => toRemove.Any(sm => cvm.Model == sm)));
+ RootList.RemoveMany(RootList.Items.Where(cvm => toRemove.Any(sm => cvm.Model == sm)));
var toRestore = args.EventArgs.RestoredCoins;
- AddCoins(toRestore);
+ RootList.AddRange(toRestore.Select(coin => new CoinViewModel(this, coin)));
+
+ SetSelections();
+ SetCoinJoinStatusWidth();
}
catch (Exception ex)
{
@@ -499,7 +498,10 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
try
{
var toRemove = args.EventArgs.Remove;
- RemoveCoins(RootList.Items.Where(cvm => toRemove.Any(sm => cvm.Model == sm)));
+ RootList.RemoveMany(RootList.Items.Where(cvm => toRemove.Any(sm => cvm.Model == sm)));
+
+ SetSelections();
+ SetCoinJoinStatusWidth();
}
catch (Exception ex)
{
@@ -542,21 +544,9 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
SetSelections();
}
- private void ClearRootList()
- {
- IEnumerable<CoinViewModel> toRemoves = RootList?.Items?.ToList() ?? Enumerable.Empty<CoinViewModel>(); // Clone so we can unsubscribe after clear.
- RootList?.Clear();
-
- foreach (CoinViewModel toRemove in toRemoves)
- {
- toRemove.UnsubscribeEvents();
- }
- }
-
public void OnClose()
{
- ClearRootList();
-
+ RootList.Clear(); // This must be called to trigger the OnItemRemoved for every items in the list.
Disposables?.Dispose();
Disposables = null;
}
@@ -602,35 +592,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
if (!cvm.Unspent)
{
- RemoveCoins(new[] { cvm });
- }
- else
- {
- SetSelections();
- SetCoinJoinStatusWidth();
- }
- }
-
- private void RemoveCoins(IEnumerable<CoinViewModel> cvms)
- {
- foreach (var cvm in cvms)
- {
- cvm.IsSelected = false;
RootList.Remove(cvm);
- cvm.UnsubscribeEvents();
}
+
SetSelections();
SetCoinJoinStatusWidth();
}
-
- private void AddCoins(IEnumerable<SmartCoin> coins)
- {
- foreach (var coin in coins)
- {
- var newCoinVm = new CoinViewModel(this, coin);
- newCoinVm.SubscribeEvents();
- RootList.Add(newCoinVm);
- }
- }
}
}

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
@@ -14,9 +14,9 @@ using WalletWasabi.Models;
namespace WalletWasabi.Gui.Controls.WalletExplorer
{
- public class CoinViewModel : ViewModelBase
+ public class CoinViewModel : ViewModelBase, IDisposable
{
- public CompositeDisposable Disposables { get; set; }
+ private CompositeDisposable Disposables { get; set; }
private bool _isSelected;
private SmartCoinStatus _status;
@@ -25,28 +25,14 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private ObservableAsPropertyHelper<bool> _confirmed;
private ObservableAsPropertyHelper<bool> _unavailable;
private CoinListViewModel _owner;
- private object DisposablesLock { get; }
public Global Global => _owner.Global;
public CoinViewModel(CoinListViewModel owner, SmartCoin model)
{
Model = model;
_owner = owner;
- DisposablesLock = new object();
- }
-
- public void SubscribeEvents()
- {
- lock (DisposablesLock)
- {
- if (Disposables != null)
- {
- throw new Exception("Please report to Dan");
- }
-
Disposables = new CompositeDisposable();
- //TODO defer subscription to when accessed (will be faster in ui.)
_coinJoinInProgress = Model
.WhenAnyValue(x => x.CoinJoinInProgress)
.ToProperty(this, x => x.CoinJoinInProgress, scheduler: RxApp.MainThreadScheduler)
@@ -114,16 +100,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
this.RaisePropertyChanged(nameof(Clusters));
}).DisposeWith(Disposables);
}
- }
-
- public void UnsubscribeEvents()
- {
- lock (DisposablesLock)
- {
- Disposables?.Dispose();
- Disposables = null;
- }
- }
public SmartCoin Model { get; }
@@ -262,5 +238,30 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
}
}
}
+
+ #region IDisposable Support
+
+ private volatile bool _disposedValue = false;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ Disposables?.Dispose();
+ }
+
+ Disposables = null;
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ #endregion IDisposable Support
}
}

Dispose properly and clearly.