Skip to content

Commit 0656885

Browse files
committed
Updated ObservableObject docs
1 parent 1c5a2f4 commit 0656885

File tree

1 file changed

+18
-6
lines changed

1 file changed

+18
-6
lines changed

docs/mvvm/ObservableObject.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,24 @@ public class ObservableUser : ObservableObject
5353
public string Name
5454
{
5555
get => user.Name;
56-
set => Set(() => user.Name, value);
56+
set => Set(user.Name, value, user, (u, n) => u.Name = n);
5757
}
5858
}
5959
```
6060

61-
The `SetProperty<T>(Expression<Func<T>>, T, string)` method makes creating these wrapping properties extremely simple, as it takes care of both retrieving and setting the target properties while providing an extremely compact API.
61+
In this case we're using the `SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)` overload. The signature is slightly more complex than the previous one - this is necessary to let the code still be extremely efficient even if we don't have access to a backing field like in the previous scenario. We can go through each part of this method signature in detail to understand the role of the different components:
62+
63+
- `TModel` is a type argument, indicating the type of the model we're wrapping. In this case, it'll be our `User` class. Note that we don't need to specify this explicitly - the C# compiler will infer this automatically by how we're invoking the `SetProperty` method.
64+
- `T` is the type of the property we want to set. Similarly to `TModel`, this is inferred automatically.
65+
- `T oldValue` is the first parameter, and in this case we're using `user.Name` to pass the current value of that property we're wrapping.
66+
- `T newValue` is the new value to set to the property, and here we're passing `value`, which is the input value within the property setter.
67+
- `TModel model` is the target model we are wrapping, in this case we're passing the instance stored in the `user` field.
68+
- `Action<TModel, T> callback` is a function that will be invoked if the new value of the property is different than the current one, and the property needs to be set. This will be done by this callback function, which receives as input the target model and the new property value to set. In this case we're just assigning the input value (which we called `n`) to the `Name` property (by doing `u.Name = n`). It is important here to avoid capturing values from the current scope and only interact with the ones given as input to the callback, as this allows the C# compiler to cache the callback function and perform a number of performance improvements. It's because of this that we're not just directly accessing the `user` field here or the `value` parameter in the setter, but instead we're only using the input parameters for the lambda expression.
69+
70+
The `SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)` method makes creating these wrapping properties extremely simple, as it takes care of both retrieving and setting the target properties while providing an extremely compact API.
71+
72+
> [!NOTE]
73+
> Compared to the implementation of this method using LINQ expressions, specifically through a parameter of type `Expression<Func<T>>` instead of the state and callback parameters, the performance improvements that can be achieved this way are really significant. In particular, this version is ~200x faster than the one using LINQ expressions, and does not make any memory allocations at all.
6274
6375
## Handling `Task<T>` properties
6476

@@ -67,12 +79,12 @@ If a property is a `Task` it's necessary to also raise the notification event on
6779
```csharp
6880
public class MyModel : ObservableObject
6981
{
70-
private Task<int> requestTask;
82+
private TaskNotifier<int>? requestTask;
7183

72-
public Task<int> RequestTask
84+
public Task<int>? RequestTask
7385
{
7486
get => requestTask;
75-
set => SetPropertyAndNotifyOnCompletion(ref requestTask, () => requestTask, value);
87+
set => SetPropertyAndNotifyOnCompletion(ref requestTask, value);
7688
}
7789

7890
public void RequestValue()
@@ -82,7 +94,7 @@ public class MyModel : ObservableObject
8294
}
8395
```
8496

85-
Here the `SetPropertyAndNotifyOnCompletion<TTask>(ref TTask, Expression<Func<TTask>>, TTask, string)` method will take care of updating the target field, monitoring the new task, if present, and raising the notification event when that task completes. This way, it's possible to just bind to a task property and to be notified when its status changes.
97+
Here the `SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string)` method will take care of updating the target field, monitoring the new task, if present, and raising the notification event when that task completes. This way, it's possible to just bind to a task property and to be notified when its status changes. The `TaskNotifier<T>` is a special type exposed by `ObservableObject` that wraps a target `Task<T>` instance and enables the necessary notification logic for this method. The `TaskNotifier` type is also available, along with two more overloads accepting that as a parameter, to wrap non-generic `Task` values.
8698

8799
> [!NOTE]
88100
> The `SetPropertyAndNotifyOnCompletion` method is meant to replace the usage of the `NotifyTaskCompletion<T>` type from the `Microsoft.Toolkit` package. If this type was being used, it can be replaced with just the inner `Task` (or `Task<TResult>`) property, and then the `SetPropertyAndNotifyOnCompletion` method can be used to set its value and raise notification changes. All the properties exposed by the `NotifyTaskCompletion<T>` type are available directly on `Task` instances.

0 commit comments

Comments
 (0)