Package Description
License
—
Deps
0
Install Size
—
Vulns
✓ 0
Published
Mar 12, 2023
$ dotnet add package ObservableHashListConsider a simple MVVM ViewModel that polls some data every 5 seconds. For example:
internal class MainWindowViewModel : ObservableObject
{
CancellationTokenSource _cancellationTokenSource = new();
public MainWindowViewModel() => _ = Poll();
public ObservableCollection<Person> People { get; } = new();
async Task Poll()
{
var token = _cancellationTokenSource.Token;
while (!token.IsCancellationRequested)
{
var people = await FetchPeople(token);
People.Clear();
foreach (var person in people)
People.Add(person);
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
}
Task<List<Person>> FetchPeople(CancellationToken cancellationToken)
{
var people = Enumerable.Range(1, 100).Select(i => new Person
{
Id = i,
Name = $"Person {i}"
}).ToList();
return Task.FromResult(people);
}
}
This code has some problems:
If we want to avoid all unecessary events to be raised we have to:
It should be easier than that!
Let's replace the ObservableCollection with an instance of ObservableHashList:
public class Person
{
public required int Id { get; init; }
public required string Name { get; init; }
}
internal class MainWindowViewModel : ObservableObject
{
CancellationTokenSource _cancellationTokenSource = new();
public MainWindowViewModel() => _ = Poll();
public ObservableHashList<Person> People { get; }
= ObservableHashList.New<Person>()
.WithSelectionKey(person => person.Id)
.ForEqualityCheckAlso(person => new { person.Name })
.OnUpdateReplaceItem();
async Task Poll()
{
var token = _cancellationTokenSource.Token;
while (!token.IsCancellationRequested)
{
var people = await FetchPeople(token);
People.Refresh(people);
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
}
Task<List<Person>> FetchPeople(CancellationToken cancellationToken)
{
var people = Enumerable.Range(1, 100).Select(i => new Person
{
Id = i,
Name = $"Person {i}"
}).ToList();
return Task.FromResult(people);
}
}
We have replaced the ObservableCollection with an instance of ObservableHashList. We have to specify 3 things:
.WithSelectionKey(person => person.Id). You can use also a different overload and pass an IEqualityComparer<T>..ForEqualityCheckAlso(person => new { person.Name }). You can use also a different overload and pass an IEqualityComparer<T>.The Refresh() method of the ObservableHashList is conceptually a Clear(); AddRange(people); call. This is what it does in detail:
The events are raised in a smart way. For example if only 20 consecutives elements have to be inserted, the Refesh() method raises only once the CollectionChanged event.
Note: We can exploit records:
public record Person
{
public required int Id { get; init; }
public required string Name { get; init; }
}
public ObservableHashList<Person> People { get; }
= ObservableHashList.New<Person>()
.WithSelectionKey(person => person.Id)
.WithDefaultEquality()
.OnUpdateReplaceItem();
The code above means: Add every person with different Id. If 2 people have the same id, use the default equality comparer to check if they are equal. For records that is equivalent to check the equality for all the fields, and that is ideal fo DTOs for example. If 2 elements have same selection key but are different according to the equality comparer, update the old item with the new one.
public ObservableHashList<Person> People { get; }
= ObservableHashList.New<Person>()
.WithSelectionKey(person => person.Id)
.WithDefaultEquality()
.OnUpdateReplaceItem();
OnUpdateReplaceItem means that on update (so if 2 elements have same selection key, but are not equal according to the equality comparer), the old element is removed and the newer one is added.
public ObservableHashList<Person> People { get; }
= ObservableHashList.New<Person>()
.WithSelectionKey(person => person.Id)
.WithDefaultEquality()
.OnUpdateReplaceItem();
OnUpdateDeepCopy means that all the propertise of the new element are copied into the old one recursively, invoking automatically the PropertyChanged event for every property set. The class must implement the INotifyPropertyChanged for this to work properly.
public record Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public required int Id { get; init; }
public required string Name { get; init; }
}
public ObservableHashList<Person> People { get; }
= ObservableHashList.New<Person>()
.WithSelectionKey(person => person.Id)
.WithDefaultEquality()
.OnUpdateDeepCopy();