How to make list and cache disposal more fluent? #891
-
I feel like I am missing something. I've found For example, I have a service that produces new immutable states that I enumerate into a list of items with a key. I want the new list to replace the old list of items so that items that are removed get removed instead of remaining in a stale state. So I have: service.StateOverTime()
.Select(t =>
CreateVms(t) // IEnumerable
.ToObservable()
.ToObservableChangeSet()
.AsObservableList()) // 👈 how to dispose of this?
.Switch() // replace old list
.AddKey(x => x.Key)
.AsObservableCache(); My understanding of the Questions:
|
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 7 replies
-
The general wisdom for RX in general, in .NET, is that you don't really need to dispose of source objects, like Bottom line, it's worth disposing of your source objects, for clarity and cleanliness, but it doesn't actually really matter, if all other best-practices are being followed.
A leak would only occur if you have a source object that you don't intend to ever use again, but don't dispose it AND don't allow it to be GC'd AND the only thing holding the downstream object alive is the reference that the source has.
In your example, it's as simple as adding a call to service.StateOverTime()
.Select(t => CreateVms(t)
.ToObservable()
.ToObservableChangeSet()
.AsObservableList()
.DisposeMany())
.Switch()
.AddKey(x => x.Key)
.AsObservableCache();
On an entirely separate note, I'd be curious to know what your usage scenario here is, because constantly rebuilding these collections of VMs seems horribly-inefficient. Pretty much anything involving the "List" side of Dynamic Data is inefficient, and not recommended. You should really avoid using Dynamic Data Lists as much as possible, unless it's truly impossible for you to derive keys for a set of items. |
Beta Was this translation helpful? Give feedback.
-
Direct responses then explanation of my actual goal is at the end. I think the problem is my true source is a service that is long running. And commonly in my code the list/cache is an intermediate step based on a subscription to that service. So if I don't dispose of them, they never unsubscribe and then leak. Granted in the example I gave, the list is attached to an For the record, What I actually want: Since the items are the real meat, my first attempt (not what was shown above) focused on the item viewmodels. I attempted to use a keyed cache to get each item viewmodel to replace equivalent ones as they get updated and then I used So I ended up just removing the second layer of ObservableCollection bindings in each group viewmodel and instead just generating the group with the new items each time. I agree its not as efficient on the UI side but in this case there is no state in the viewmodels. They are basically immutable display only fields. |
Beta Was this translation helpful? Give feedback.
-
Aha! Okay, so I didn't realize there's a dedicated As far as However, looking at this version of
Alright, so something like this... public record MyDataItem
{
public int Id { get; init; }
public string Name { get; init; }
public string GroupName { get; init; }
}
public record MyDataState
{
public ImmutableList<MyDataItem> Items { get; init; }
}
public class MyDataStateService
{
public IObservable<MyDataState> StateOverTime();
} And what you want in the UI is something like... public class MyMainModel
{
public ObservableCollection<MyItemGroupModel> Groups { get; }
}
public class MyItemGroupModel
{
public string GroupName { get; }
public ObservableCollection<MyItemViewModel> Items { get; }
}
public class MyItemViewModel
: INotifyPropertyChanged
{
public int Id { get; }
public string Name { get; }
} Does this reasonably represent your scenario? |
Beta Was this translation helpful? Give feedback.
-
Yep! That is a good model to use as an example. Thanks for taking the time to put that together! On the topic of |
Beta Was this translation helpful? Give feedback.
-
So, the first and most obvious thing I'd recommend is breaking apart your state model. I've tried version of a fully-centralized state (that seems to be what you're going for? with To be more specific, I'm saying that if you're gonna use Dynamic Data, pull stuff that you intend to use with Dynamic Data out of the state tree, and it becomes WAY easier to manage. public class MyDataStateService
{
public IObservable<MySimpleDataState> StateOverTime();
public ISourceCache<int, MyDataItem> ItemsByKey { get; }
} Reduce If this really isn't feasible for you, then your goal is to mimic this setup as closely as possible, I.E. get those items into an var itemsByKey = stateService
.StateOverTime()
.Select(state => state.Items)
.DistinctUntilChanged()
.EditDiff(item => item.Id)
.AsObservableCache(); // No need for this if you're only going to have one downstream subscriber. The Now, we can build a much more straightforward DD stream to group up items and build VMs. public sealed class MyMainModel
: IDisposable
{
public MyMainModel(IObservableCache<int, MyDataItem> itemsByKey)
{
itemsByKey
.Connect()
.Group(item => item.GroupName)
.Transform(group =>new MyItemGroupModel(group))
.DisposeMany()
.Bind(out var Groups)
.Subscribe()
.DisposeWith(Subscriptions);
}
public ObservableCollection<MyItemGroupModel> Groups { get; }
} The Your situation might be a little more complex than just grouping on a single Let's expand on what public sealed class MyItemGroupModel
: IDisposable
{
public MyItemGroupModel(IGroup<MyDataItem, int, string> group)
{
GroupName = group.Key;
group.Cache
.Connect()
.DistinctValues(item => item.Id)
.Transform(itemId => new MyItemViewModel(itemId, group.Cache))
.DisposeMany()
.Bind(out Items)
.Subscribe()
.DisposeWith(Subscriptions);
}
public string GroupName { get; }
public ObservableCollection<MyItemViewModel> Items { get; }
} Here, I'm making a point to avoid some VM churn by ignoring all changes except add and remove of unique ID values, I.E. VMs only get constructed when an item is first added, and only get removed when the item is removed, as keyed by ID. Item refreshes and replaces along the way get handled within the VM itself, as follows: public sealed class MyItemViewModel
: INotifyPropertyChanged,
IDisposable
{
public MyItemViewModel(
int id,
IObservableCache<MyDataItem> items)
{
Id = id;
items
.Watch(id)
.Select(change => change.Current.Name)
.DistinctUntilChanged()
.Subscribe(name => Name = name)
.DisposeWith(Subscriptions);
}
public int Id { get; private init; }
public string Name { get; private set; }
} I'll leave the implementation of This isn't even the only possibility. Maybe it makes more sense to you to do the grouping AFTER you've transformed to VMs. In fact, that would probably produce less VM churn, if public sealed class MyMainModel
: IDisposable
{
public MyMainModel(IObservableCache<int, MyDataItem> itemsByKey)
{
itemsByKey
.Connect()
.DistinctValues(item => item.Id)
.Transform(itemId => new MyItemViewModel(itemId, itemsByKey))
.GroupOnObservable(item => item.WhenAnyValue(item => item.GroupName))
.Transform(group =>new MyItemGroupModel(group))
.DisposeMany()
.Bind(out var Groups)
.Subscribe()
.DisposeWith(Subscriptions);
}
public ObservableCollection<MyItemGroupModel> Groups { get; }
} With this, |
Beta Was this translation helpful? Give feedback.
I was indeed missing something.
.AsObservableList()
returns anIObservableList<>
which isIDisposable
, so yeah, you do potentially need to handle disposal of that. Practically speaking, you could just... not. Disposing them won't really do anything important, the.Switch()
will take care of cleanup of the subscriptions, and that's all that's really important.But the truth is, it doesn't even matter. Because you can, in fact, just get rid of
.AsObservableList()
completely there, and then nothing disposable gets created in the first place