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 e INotifyPropertyChanging, che espone gli PropertyChanged eventi e PropertyChanging .
  • Fornisce una serie di metodi che possono essere usati per impostare facilmente i valori delle SetProperty proprietà dai tipi che ereditano da ObservableObjecte per generare automaticamente gli eventi appropriati.
  • Fornisce il SetPropertyAndNotifyOnCompletion metodo, analogo a SetProperty ma con la possibilità di impostare Task le proprietà e generare automaticamente gli eventi di notifica al termine delle attività assegnate.
  • Espone i metodi OnPropertyChanged e OnPropertyChanging, che possono essere sovrascritti nei tipi derivati per personalizzare il modo in cui vengono generati gli 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.

Incapsulamento di un modello non osservabile

Uno scenario tipico, ad esempio, quando si lavora con elementi del database, consiste nel creare un modello wrapper "collegabile" che espone le proprietà del modello di database e genera 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 stiamo usando il sovraccarico 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 di modello che stiamo incapsulando. In questo caso, sarà la nostra User classe. Si noti che non è necessario specificare questo comportamento in modo esplicito. Il compilatore C# dedurrà automaticamente questa operazione richiamando il SetProperty metodo.
  • T è il tipo della proprietà da impostare. Analogamente a TModel, questo viene dedotto automaticamente.
  • T oldValue è il primo parametro e in questo caso viene usato user.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 passato value, ovvero il valore di input all'interno del setter della proprietà.
  • TModel model è il modello di destinazione che stiamo incapsulando; in questo caso stiamo passando l'istanza memorizzata nel campo user.
  • 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 stiamo semplicemente assegnando il valore di input (che abbiamo chiamato n) alla proprietà Name (facendo 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 al user campo qui o al value 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à è un Task, è necessario generare anche l'evento di notifica una volta completata l'attività, in modo che i binding vengano aggiornati al momento giusto, ad esempio per visualizzare un indicatore di caricamento o altre informazioni di stato relative all'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 associarsi semplicemente a una proprietà dell'attività e ricevere una notifica quando il suo stato cambia. 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

  • Scopri l'app di esempio (per diversi framework dell'interfaccia utente) per vedere il MVVM Toolkit in azione.
  • È anche possibile trovare altri esempi negli unit test.