ObservableObject
ObservableObject
est une classe de base pour les objets qui sont observables en implémentant les interfaces INotifyPropertyChanged
et INotifyPropertyChanging
. Elle peut servir de point de départ pour tous les types d’objets qui doivent prendre en charge les notifications de modification de propriété.
API de plateforme :
ObservableObject
,TaskNotifier
,TaskNotifier<T>
Fonctionnement
Voici les principales caractéristiques de ObservableObject
:
- Elle fournit une implémentation de base pour
INotifyPropertyChanged
etINotifyPropertyChanging
, exposant les événementsPropertyChanged
etPropertyChanging
. - Elle propose une série de méthodes
SetProperty
qui permettent de définir facilement des valeurs de propriétés à partir de types héritant deObservableObject
, et de déclencher automatiquement les événements appropriés. - Elle fournit la méthode
SetPropertyAndNotifyOnCompletion
, qui est comparable àSetProperty
, mais avec en outre la possibilité de définir des propriétésTask
et de déclencher automatiquement les événements de notification lorsque les tâches assignées sont terminées. - Elle expose les méthodes
OnPropertyChanged
etOnPropertyChanging
, qui peuvent être remplacées dans les types dérivés pour personnaliser la façon dont les événements de notification sont déclenchés.
Propriété simple
Voici un exemple d’implémentation de la prise en charge des notifications dans une propriété personnalisée :
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
La méthode SetProperty<T>(ref T, T, string)
fournie vérifie la valeur actuelle de la propriété, la met à jour si elle est différente et déclenche automatiquement les événements correspondants. Le nom de la propriété étant automatiquement capturé via l’attribut [CallerMemberName]
, il n’est pas nécessaire de préciser manuellement la propriété qui doit être mise à jour.
Enveloppement d’un modèle non observable
La création d’un modèle enveloppant pouvant être lié, qui relaie les propriétés du modèle de base de données et qui déclenche si nécessaire les notifications de modification de propriété est un exemple de scénario courant qui est nécessaire dans le cadre de l’utilisation d’éléments de base de données. Cela est également nécessaire lorsqu’il s’agit d’injecter la prise en charge des notifications à des modèles qui n’implémentent pas l’interface INotifyPropertyChanged
. ObservableObject
propose une méthode dédiée qui simplifie ce processus. Dans l’exemple suivant, User
est un modèle qui mappe directement une table de base de données, sans hériter de ObservableObject
:
public class ObservableUser : ObservableObject
{
private readonly User user;
public ObservableUser(User user) => this.user = user;
public string Name
{
get => user.Name;
set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
}
}
Dans ce cas, nous utilisons la surcharge SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
. La signature est légèrement plus complexe que la précédente (le code se doit de rester très efficace même si nous n’avons pas accès à un champ de stockage comme dans le scénario précédent). Nous pouvons examiner dans les détails chaque partie de cette signature de méthode afin de comprendre le rôle des différents composants :
TModel
est un argument de type, qui indique le type du modèle que nous enveloppons. Dans ce cas, il s’agit de la classeUser
. Notez qu’il n’est pas nécessaire de le spécifier explicitement : le compilateur C# le déduit automatiquement selon la façon dont nous appelons la méthodeSetProperty
.T
est le type de la propriété que nous devons définir. Comme pourTModel
, il est déduit automatiquement.T oldValue
est le premier paramètre. Dans ce cas, nous utilisonsuser.Name
pour transmettre la valeur actuelle de cette propriété que nous enveloppons.T newValue
est la nouvelle valeur à attribuer à la propriété. Ici, nous transmettonsvalue
, qui est la valeur d’entrée dans le setter de la propriété.TModel model
est le modèle cible que nous enveloppons. Dans ce cas, nous transmettons l’instance stockée dans le champuser
.Action<TModel, T> callback
est une fonction qui est appelée si la nouvelle valeur de la propriété est différente de l’actuelle et si la propriété a besoin d’être définie. C’est cette fonction de rappel qui s’en charge, laquelle reçoit en entrée le modèle cible et la nouvelle valeur de propriété à définir. Dans ce cas, nous attribuons simplement la valeur d’entrée (que nous avons appeléen
) à la propriétéName
(en indiquantu.Name = n
). Il est important ici d’éviter de capturer les valeurs de l’étendue actuelle et d’interagir uniquement avec celles fournies en entrée au rappel, car cela permet au compilateur C# de mettre en cache la fonction de rappel et d’apporter un certain nombre d’améliorations sur le plan des performances. C’est pour cette raison que nous n’accédons pas ici directement au champuser
ou au paramètrevalue
dans le setter. Au lieu de cela, nous utilisons uniquement les paramètres d’entrée pour l’expression lambda.
La méthode SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
facilite considérablement la création de ces propriétés d’enveloppement, car elle se charge de récupérer et de définir les propriétés cibles tout en fournissant une API extrêmement compacte.
Remarque
Par rapport à l’implémentation de cette méthode avec des expressions LINQ, en particulier par l’intermédiaire d’un paramètre de type Expression<Func<T>>
au lieu des paramètres d’état et de rappel, les améliorations potentielles sur le plan des performances sont véritablement significatives. Pour être plus précis, cette version est environ 200 fois plus rapide que celle utilisant des expressions LINQ et ne procède à aucune allocation de mémoire.
Gestion des propriétés Task<T>
Si une propriété est de type Task
, il est nécessaire de déclencher également l’événement de notification une fois la tâche terminée. Cela permet ainsi aux liaisons d’être mises à jour au bon moment, par exemple pour afficher un indicateur de chargement ou toute autre information d’état sur l’opération représentée par la tâche. ObservableObject
propose une API pour ce scénario :
public class MyModel : ObservableObject
{
private TaskNotifier<int>? requestTask;
public Task<int>? RequestTask
{
get => requestTask;
set => SetPropertyAndNotifyOnCompletion(ref requestTask, value);
}
public void RequestValue()
{
RequestTask = WebService.LoadMyValueAsync();
}
}
Ici, la méthode SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string)
se charge de mettre à jour le champ cible, de surveiller la nouvelle tâche, le cas échéant, et de déclencher l’événement de notification une fois cette tâche terminée. Cela permet de se lier simplement à une propriété de tâche et d’être notifié lorsque son état change. TaskNotifier<T>
est un type spécial exposé par ObservableObject
qui enveloppe une instance Task<T>
cible et met en œuvre la logique de notification nécessaire pour cette méthode. Le type TaskNotifier
peut aussi être utilisé directement si vous disposez uniquement d’un Task
général.
Remarque
La méthode SetPropertyAndNotifyOnCompletion
vise à remplacer l’utilisation du type NotifyTaskCompletion<T>
du package Microsoft.Toolkit
. Si ce type était utilisé, il est possible de le remplacer simplement par la propriété interne Task
(ou Task<TResult>
). La méthode SetPropertyAndNotifyOnCompletion
peut alors être utilisée pour définir sa valeur et déclencher des changements de notifications. Toutes les propriétés exposées par le type NotifyTaskCompletion<T>
sont directement disponibles au niveau des instances Task
.
Exemples
- Consultez l’exemple d’application (pour plusieurs infrastructures d’interface utilisateur) afin de voir le Kit d’outils Modèle-vue-vue modèle (MVVM) en action.
- Vous trouverez également d’autres exemples dans les tests unitaires.