Condividi tramite


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 OnPropertyChanged metodi e OnPropertyChanging , 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 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 di cui viene eseguito il wrapping, in questo caso si passa l'istanza archiviata nel user 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 chiamato n) alla Name 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 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à è 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