Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
A ObservableObject é uma classe base para objetos que são observáveis ao implementar as interfaces INotifyPropertyChanged e INotifyPropertyChanging. Pode ser usado como ponto de partida para todo o tipo de objetos que precisam de suportar notificações de alteração de propriedade.
APIs da plataforma:
ObservableObject,TaskNotifier,TaskNotifier<T>
Como funciona
ObservableObject possui as seguintes características principais:
- Fornece uma implementação base para
INotifyPropertyChangedeINotifyPropertyChanging, expondo osPropertyChangedeventos ePropertyChanging. - Fornece uma série de
SetPropertymétodos que podem ser usados para definir facilmente valores de propriedades a partir de tipos que herdam deObservableObject, e para gerar automaticamente os eventos apropriados. - Fornece o método
SetPropertyAndNotifyOnCompletion, que é análogo aSetProperty, mas com a capacidade de definir propriedadesTaske gerar automaticamente os eventos de notificação quando as tarefas atribuídas são concluídas. - Expõe os métodos
OnPropertyChangedeOnPropertyChanging, que podem ser substituídos em tipos derivados para personalizar como os eventos de notificação são disparados.
Propriedade simples
Aqui está um exemplo de como implementar suporte de notificações para uma propriedade personalizada:
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
O método fornecido SetProperty<T>(ref T, T, string) verifica o valor atual da propriedade, atualiza-o se for diferente, e depois também aumenta automaticamente os eventos relevantes. O nome da propriedade é automaticamente capturado através do uso do [CallerMemberName] atributo, por isso não é necessário especificar manualmente qual a propriedade que está a ser atualizada.
Encapsular um modelo não observável
Um cenário comum, por exemplo, ao trabalhar com itens de uma base de dados, consiste em criar um modelo "associável" de encapsulamento que retransmita as propriedades do modelo da base de dados e dispare as notificações de alteração de propriedade quando necessário. Isto também é necessário quando se quer injetar suporte de notificações em modelos que não implementam a INotifyPropertyChanged interface.
ObservableObject fornece um método dedicado para tornar este processo mais simples. Para o exemplo seguinte, User é um modelo que mapeia diretamente uma tabela de base de dados, sem herdar 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);
}
}
Neste caso, estamos a usar a versão sobrecarregada SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string). A assinatura é ligeiramente mais complexa do que a anterior – isto é necessário para que o código continue extremamente eficiente, mesmo que não tenhamos acesso a um campo de apoio como no cenário anterior. Podemos analisar cada parte desta assinatura de método em detalhe para compreender o papel dos diferentes componentes:
-
TModelé um argumento de tipo, indicando o tipo do modelo que estamos a envolver. Neste caso, será a nossaUserclasse. Note que não precisamos de especificar isto explicitamente – o compilador C# irá inferir isto automaticamente pela forma como estamos a invocar oSetPropertymétodo. -
Té o tipo de propriedade que queremos definir. De forma semelhante aTModel, isto é inferido automaticamente. -
T oldValueé o primeiro parâmetro e, neste caso, estamos a usaruser.Namepara passar o valor atual dessa propriedade que estamos a envolver. -
T newValueé o novo valor a definir para a propriedade, e aqui estamos a passarvalue, que é o valor de entrada dentro do definidor de propriedades. -
TModel modelé o modelo alvo que estamos a envolver, neste caso estamos a passar a instância armazenada nousercampo. -
Action<TModel, T> callbacké uma função que será invocada se o novo valor da propriedade for diferente do atual, e a propriedade tiver de ser definida. Isto será feito através desta função de callback, que recebe como parâmetros o modelo de destino e o novo valor a atribuir à propriedade. Neste caso, estamos apenas a atribuir o valor de entrada (que chamámosn) àNamepropriedade (fazendou.Name = n). É importante, neste caso, evitar capturar valores do contexto atual e interagir apenas com os que são fornecidos como entrada à callback, pois isso permite ao compilador de C# colocar a função de callback em cache e efetuar várias otimizações de desempenho. É por isso que não estamos apenas a aceder diretamente aousercampo aqui ou aovalueparâmetro no setter, mas sim apenas a usar os parâmetros de entrada para a expressão lambda.
O SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) método torna a criação destas propriedades de wrapping extremamente simples, pois trata tanto da recuperação como da definição das propriedades alvo, ao mesmo tempo que fornece uma API extremamente compacta.
Observação
Comparado com a implementação deste método usando expressões LINQ, especificamente através de um parâmetro de tipo Expression<Func<T>> em vez dos parâmetros de estado e callback, as melhorias de desempenho que podem ser alcançadas desta forma são realmente significativas. Em particular, esta versão é ~200x mais rápida do que a que usa expressões LINQ e não faz quaisquer alocações de memória.
Propriedades de manuseamento Task<T>
Se uma propriedade for um Task, também é necessário disparar o evento de notificação quando a tarefa estiver concluída, para que os bindings sejam atualizados no momento certo, por exemplo, para apresentar um indicador de carregamento ou outras informações de estado relativas à operação representada pela tarefa.
ObservableObject tem uma API para este cenário:
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();
}
}
Aqui, o SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string) método trata de atualizar o campo de destino, monitorizar a nova tarefa, se existir, e levantar o evento de notificação quando essa tarefa for concluída. Desta forma, é possível simplesmente vincular-se a uma propriedade de uma tarefa e ser notificado quando o respetivo estado se alterar. O TaskNotifier<T> é um tipo especial exposto por ObservableObject que envolve uma instância de destino Task<T> e permite a lógica de notificação necessária para este método. O tipo TaskNotifier também está disponível para ser utilizado diretamente se tiver apenas um Task genérico.
Observação
O método SetPropertyAndNotifyOnCompletion destina-se a substituir o uso do tipo NotifyTaskCompletion<T> do pacote Microsoft.Toolkit. Se este tipo estivesse a ser usado, pode ser substituído apenas pela propriedade interna Task (ou Task<TResult>), e depois o SetPropertyAndNotifyOnCompletion método pode ser usado para definir o seu valor e gerar alterações de notificação. Todas as propriedades expostas pelo NotifyTaskCompletion<T> tipo estão disponíveis diretamente nas Task instâncias.
Exemplos
- Dá uma vista de olhos à aplicação de exemplo (para múltiplos frameworks de interface) para veres o MVVM Toolkit em ação.
- Também podes encontrar mais exemplos nos testes unitários.