ObservableObject
O 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 todos os tipos de objetos que precisam dar suporte a notificações de alteração de propriedade.
APIs de plataforma:
ObservableObject
,TaskNotifier
,TaskNotifier<T>
Como ele funciona
O ObservableObject
tem os seguintes recursos principais:
- Ele fornece uma implementação base para
INotifyPropertyChanged
eINotifyPropertyChanging
, expondo os eventosPropertyChanged
ePropertyChanging
. - Fornece uma série de métodos
SetProperty
que podem ser usados para definir facilmente valores da propriedade de tipos herdados deObservableObject
, e para gerar automaticamente os eventos apropriados. - Fornece o método
SetPropertyAndNotifyOnCompletion
, que é análogo aoSetProperty
mas com a capacidade de definir as propriedadesTask
e gerar os eventos de notificação automaticamente quando as tarefas atribuídas forem concluídas. - Expõe os métodos
OnPropertyChanged
eOnPropertyChanging
, que podem ser substituídos em tipos derivados para personalizar como os eventos de notificação são gerados.
Propriedade simples
Aqui está um exemplo de como implementar o suporte à notificação para uma propriedade personalizada:
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
O método SetProperty<T>(ref T, T, string)
fornecido verifica o valor atual da propriedade e o atualiza se for diferente e também gera automaticamente os eventos relevantes. O nome da propriedade é capturado automaticamente por meio do uso do atributo [CallerMemberName]
, portanto, não há necessidade de especificar manualmente qual propriedade está sendo atualizada.
Encapsulamento de um modelo não observável
Um cenário comum, por exemplo, ao trabalhar com itens de banco de dados, é criar um modelo "associável" de empacotamento que retransmite as propriedades do modelo de banco de dados e gera notificações de alteração da propriedade quando necessário. Isso também é necessário quando se deseja injetar suporte de notificação em modelos que não implementam a interface INotifyPropertyChanged
. ObservableObject
fornece um método dedicado para tornar esse processo mais simples. Para o exemplo a seguir, User
é um modelo que mapeia diretamente uma tabela de banco 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);
}
}
Nesse caso, usamos a sobrecarga SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
. A assinatura é um pouco mais complexa que a anterior - isso é necessário para deixar o código ainda ser extremamente eficiente mesmo que não tenhamos acesso a um campo de suporte como no cenário anterior. Podemos examinar detalhadamente cada parte dessa assinatura de método para entender a função dos diferentes componentes:
TModel
é um argumento de tipo, que indica o tipo do modelo que estamos encapsulando. Nesse caso, será nossa classeUser
. Observe que não precisamos especificar isso explicitamente - o compilador C# inferirá isso automaticamente pela forma como invocamos o métodoSetProperty
.T
é o tipo da propriedade que desejamos definir. Da mesma forma queTModel
, isso é inferido automaticamente.T oldValue
é o primeiro parâmetro e, nesse caso, usamosuser.Name
para passar o valor atual dessa propriedade que estamos encapsulando.T newValue
é o novo valor a ser definido como a propriedade e aqui passamosvalue
, que é o valor de entrada dentro do setter da propriedade.TModel model
é o modelo de destino que estamos encapsulando, nesse caso, passamos a instância armazenada no campouser
.Action<TModel, T> callback
é uma função que será invocada se o novo valor da propriedade for diferente do atual e a propriedade precisar ser definida. Isso será feito por esta função de retorno de chamada, que recebe como entrada o modelo alvo e o novo valor da propriedade a ser definido. Nesse caso, apenas atribuímos o valor de entrada (que chamamosn
) à propriedadeName
(atribuindou.Name = n
). É importante evitar a captura de valores do escopo atual e interagir apenas com aqueles fornecidos como entrada para o retorno de chamada, pois isso permite que o compilador C# armazene em cache a função de retorno de chamada e execute uma série de melhorias de desempenho. É por isso que não acessamos diretamente aqui apenas o campouser
ou o parâmetrovalue
no setter, mas usamos apenas os parâmetros de entrada da expressão lambda.
O método SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
torna a criação dessas propriedades de encapsulamento extremamente simples, pois cuida da recuperação e da definição das propriedades de destino, fornecendo uma API extremamente compacta.
Observação
Em comparação com a implementação desse método que usa expressões LINQ, especificamente por meio de um parâmetro de tipo Expression<Func<T>>
em vez dos parâmetros de estado e de retorno de chamada, as melhorias de desempenho que podem ser obtidas dessa maneira são realmente significativas. Em particular, esta versão é aproximadamente 200x mais rápida que aquela que usa expressões LINQ e não faz nenhuma alocação de memória.
Manipulação de propriedades Task<T>
Se uma propriedade for um Task
também será necessário gerar o evento de notificação assim que a tarefa for concluída, para que as associações sejam atualizadas no momento certo, por exemplo, para exibir um indicador de carregamento ou outras informações de status na 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 método SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string)
cuidará da atualização do campo de destino, do monitoramento da nova tarefa, se presente, e da geração do evento de notificação quando essa tarefa for concluída. Dessa forma, é possível apenas associar a uma propriedade de tarefa e ser notificado quando seu status for alterado. O TaskNotifier<T>
é um tipo especial exposto pelo ObservableObject
que encapsula uma instância de Task<T>
de destino e habilita a lógica de notificação necessária para esse método. O tipo TaskNotifier
também estará disponível para uso diretamente se você tiver apenas um Task
geral.
Observação
O método SetPropertyAndNotifyOnCompletion
destina-se a substituir o uso do tipo NotifyTaskCompletion<T>
do pacote Microsoft.Toolkit
. Se esse tipo estiver sendo usado, poderá ser substituído apenas pela propriedade interna Task
(ou Task<TResult>
) e o método SetPropertyAndNotifyOnCompletion
poderá ser usado para definir seu valor e gerar alterações de notificação. Todas as propriedades expostas pelo tipo NotifyTaskCompletion<T>
estão disponíveis diretamente em instâncias de Task
.
Exemplos
- Confira o aplicativo de exemplo (para várias estruturas de interface do usuário) para ver o Kit de Ferramentas MVVM em ação.
- Você também pode encontrar mais exemplos nos testes de unidade.