ObservableObject
ObservableObject
è una classe di base per gli oggetti osservabili implementando le INotifyPropertyChanged
interfacce e INotifyPropertyChanging
. Può essere usato come punto di partenza per tutti i tipi di oggetti che devono supportare le notifiche di modifica delle proprietà.
API della piattaforma:
ObservableObject
,TaskNotifier
,TaskNotifier<T>
Funzionamento
ObservableObject
presenta le funzionalità principali seguenti:
- Fornisce un'implementazione di base per
INotifyPropertyChanged
eINotifyPropertyChanging
, che espone gliPropertyChanged
eventi ePropertyChanging
. - Fornisce una serie di metodi che possono essere usati per impostare facilmente i valori delle
SetProperty
proprietà dai tipi che ereditano daObservableObject
e per generare automaticamente gli eventi appropriati. - Fornisce il
SetPropertyAndNotifyOnCompletion
metodo, analogo aSetProperty
ma con la possibilità di impostareTask
le proprietà e generare automaticamente gli eventi di notifica al termine delle attività assegnate. - Espone i
OnPropertyChanged
metodi eOnPropertyChanging
, che possono essere sottoposti a override nei tipi derivati, per personalizzare la modalità di generazione degli eventi di notifica.
Proprietà semplice
Ecco un esempio di come implementare il supporto delle notifiche a una proprietà personalizzata:
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
Il metodo fornito SetProperty<T>(ref T, T, string)
controlla il valore corrente della proprietà e lo aggiorna se diverso e quindi genera automaticamente gli eventi pertinenti. Il nome della proprietà viene acquisito automaticamente tramite l'uso dell'attributo [CallerMemberName]
, quindi non è necessario specificare manualmente la proprietà da aggiornare.
Wrapping di un modello non osservabile
Uno scenario comune, ad esempio, quando si lavora con gli elementi di database, consiste nel creare un modello di wrapping "associabile" che inoltra le proprietà del modello di database e genera le notifiche di modifica delle proprietà quando necessario. Questa operazione è necessaria anche quando si vuole inserire il supporto delle notifiche ai modelli, che non implementano l'interfaccia INotifyPropertyChanged
. ObservableObject
fornisce un metodo dedicato per semplificare questo processo. Per l'esempio seguente, User
è un modello che esegue direttamente il mapping di una tabella di database, senza ereditare da 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);
}
}
In questo caso viene usato l'overload SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
. La firma è leggermente più complessa rispetto a quella precedente. Ciò è necessario per consentire al codice di essere ancora estremamente efficiente anche se non si ha accesso a un campo sottostante come nello scenario precedente. È possibile esaminare in dettaglio ogni parte di questa firma del metodo per comprendere il ruolo dei diversi componenti:
TModel
è un argomento di tipo che indica il tipo del modello di cui viene eseguito il wrapping. In questo caso, sarà la nostraUser
classe. Si noti che non è necessario specificare questo comportamento in modo esplicito. Il compilatore C# dedurrà automaticamente questa operazione richiamando ilSetProperty
metodo.T
è il tipo della proprietà da impostare. Analogamente aTModel
, questo viene dedotto automaticamente.T oldValue
è il primo parametro e in questo caso viene usatouser.Name
per passare il valore corrente di tale proprietà di cui viene eseguito il wrapping.T newValue
è il nuovo valore da impostare sulla proprietà e in questo caso viene passatovalue
, ovvero il valore di input all'interno del setter della proprietà.TModel model
è il modello di destinazione di cui viene eseguito il wrapping, in questo caso si passa l'istanza archiviata neluser
campo .Action<TModel, T> callback
è una funzione che verrà richiamata se il nuovo valore della proprietà è diverso da quello corrente e la proprietà deve essere impostata. Questa operazione verrà eseguita da questa funzione di callback, che riceve come input il modello di destinazione e il nuovo valore della proprietà da impostare. In questo caso si sta semplicemente assegnando il valore di input (che è stato chiamaton
) allaName
proprietà (eseguendo ).u.Name = n
È importante evitare di acquisire valori dall'ambito corrente e interagire solo con quelli specificati come input per il callback, in quanto ciò consente al compilatore C# di memorizzare nella cache la funzione di callback ed eseguire numerosi miglioramenti delle prestazioni. È per questo motivo che non si accede direttamente aluser
campo qui o alvalue
parametro nel setter, ma si usano solo i parametri di input per l'espressione lambda.
Il SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
metodo rende estremamente semplice la creazione di queste proprietà di wrapping, in quanto si occupa sia del recupero che dell'impostazione delle proprietà di destinazione, fornendo un'API estremamente compatta.
Nota
Rispetto all'implementazione di questo metodo usando espressioni LINQ, in particolare tramite un parametro di tipo Expression<Func<T>>
anziché i parametri di stato e callback, i miglioramenti delle prestazioni che è possibile ottenere in questo modo sono davvero significativi. In particolare, questa versione è di circa 200 volte più veloce rispetto a quella che usa espressioni LINQ e non esegue alcuna allocazione di memoria.
Gestione delle Task<T>
proprietà
Se una proprietà è necessaria Task
anche per generare l'evento di notifica al termine dell'attività, in modo che le associazioni vengano aggiornate al momento giusto. Ad esempio, per visualizzare un indicatore di caricamento o altre informazioni sullo stato sull'operazione rappresentata dall'attività. ObservableObject
ha un'API per questo scenario:
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();
}
}
In questo caso, il SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string)
metodo eseguirà l'aggiornamento del campo di destinazione, il monitoraggio della nuova attività, se presente, e la generazione dell'evento di notifica al termine dell'attività. In questo modo, è possibile eseguire semplicemente l'associazione a una proprietà dell'attività e ricevere una notifica quando cambia lo stato. TaskNotifier<T>
è un tipo speciale esposto da ObservableObject
che esegue il wrapping di un'istanza di destinazione Task<T>
e abilita la logica di notifica necessaria per questo metodo. Il TaskNotifier
tipo è disponibile anche per l'uso diretto se si dispone solo di un tipo generale Task
.
Nota
Il SetPropertyAndNotifyOnCompletion
metodo è progettato per sostituire l'utilizzo del NotifyTaskCompletion<T>
tipo dal Microsoft.Toolkit
pacchetto. Se questo tipo è in uso, può essere sostituito solo con la proprietà interna Task
(o Task<TResult>
) e quindi il metodo può essere usato per impostarne il SetPropertyAndNotifyOnCompletion
valore e generare modifiche di notifica. Tutte le proprietà esposte dal NotifyTaskCompletion<T>
tipo sono disponibili direttamente nelle Task
istanze.
Esempi
- Vedere l'app di esempio (per più framework dell'interfaccia utente) per vedere MVVM Toolkit in azione.
- È anche possibile trovare altri esempi negli unit test.