11 09 2013
Utilizando CallerMemberName para simplificar a implementação de INotifyPropertyChanged
Olá pessoal, tudo certo?
Hoje quero falar com vocês sobre uma das coisas mais chatas e repetitivas que nós, desenvolvedores .NET, temos que aturar quando trabalhamos com classes que precisam notificar alterações. Hoje em dia, independente da tecnologia de UI que você esteja utilizando, muito provavelmente você vai se deparar com a necessidade de implementar a interface INotifyPropertyChanged. Essa interface é utilizada em Windows Forms, WPF, Silverlight e WinRT. Se você já teve que fazer data binding de propriedades de alguma classe com algum elemento na interface do usuário, você fatalmente já implementou essa interface e sabe o quão chato é ter que implementá-la em inúmeras propriedades da aplicação.
O artigo de hoje aborda a utilização de uma funcionalidade nova, presente no .NET Framework 4.5, que acaba nos ajudando um pouco nessa situação. Para entendermos um pouco melhor o problema, vamos criar um projeto do tipo Windows Store Blank App e, nesse projeto, vamos adicionar uma classe muito simples chamada Pessoa:
public class Pessoa { public string Nome { get; set; } public string Sobrenome { get; set; } }
Como você pode ver, para fins de simplificar o exemplo, essa classe contém somente duas propriedades: Nome e Sobrenome. Porém, essas propriedades são do tipo “automaticamente implementadas”, e não propriedades completas, ou seja, elas só têm o get/set. O primeiro passo que devemos seguir para implementar a interface INotifyPropertyChanged nessa classe é transformar essas propriedades em propriedades completas:
public class Pessoa { private string _nome; public string Nome { get { return _nome; } set { _nome = value; } } private string _sobrenome; public string Sobrenome { get { return _sobrenome; } set { _sobrenome = value; } } }
Feito isso, já podemos implementar a interface INotifyPropertyChanged na nossa classe Pessoa:
public class Pessoa : System.ComponentModel.INotifyPropertyChanged { private string _nome; public string Nome { get { return _nome; } set { _nome = value; if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Nome")); } } private string _sobrenome; public string Sobrenome { get { return _sobrenome; } set { _sobrenome = value; if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Sobrenome")); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; }
Observe que o código no setter das propriedades é praticamente idêntico. A única informação que se difere entre uma propriedade e outra é justamente o nome da propriedade. Portanto, o que costumamos fazer nesse caso é criar um método que recebe o nome da propriedade e faz o disparo do evento PropertyChanged apropriadamente:
public class Pessoa : System.ComponentModel.INotifyPropertyChanged { private string _nome; public string Nome { get { return _nome; } set { _nome = value; RaisePropertyChanged("Nome"); } } private string _sobrenome; public string Sobrenome { get { return _sobrenome; } set { _sobrenome = value; RaisePropertyChanged("Sobrenome"); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } }
Pronto. Esse é o código que provavelmente você está acostumado a escrever para implementar essa interface. Obviamente o que fazemos normalmente é criar uma classe base que implementa essa interface e, nas nossas classes, nós simplesmente herdamos dessa classe base. Se você está acostumado a trabalhar com frameworks ou toolkits MVVM, você certamente encontrará uma classe base que implementa INotifyPropertyChanged e expõe esses helpers para facilitar nossa vida. Por exemplo, o MVVM Light Toolkit tem a classe ViewModelBase, que faz justamente essa função.
Porém, como mencionei no início desse artigo, o .NET Framework 4.5 conta com uma funcionalidade que simplifica o trabalho de implementar essa interface. Essa funcionalidade se chama CallerMemberName. Primeiramente vamos entender qual é o propósito dessa funcionalidade. Observe o código a seguir:
class Program { static void Main(string[] args) { Metodo1(); Metodo2(); Console.ReadLine(); } private static void Metodo1() { Log("Execução do método 1"); } private static void Metodo2() { Log("Execução do método 2"); } private static void Log(string message, [System.Runtime.CompilerServices.CallerMemberName] string callerName = null) { System.Diagnostics.Debug.WriteLine("O método {0} logou a seguinte mensagem: {1}", callerName, message); } }
Esse código mostra um exemplo de logging em um projeto Console Application que toma vantagem do CallerMemberName. Se você quiser testar esse código, é só criar um novo projeto de Console Application e colar esse código na classe Program. Ao executar esse código e observar o conteúdo da janela Output, você consegue notar duas entradas feitas pelo método Log:
Ou seja, ao utilizar o parâmetro opcional CallerMemberName em qualquer método da sua aplicação, o conteúdo dele será preenchido com o nome do método que o chamou. E em quê isso nos ajudaria na questão da implementação da interface INotifyPropertyChanged? Oras, o método RaisePropertyChanged que construímos nos códigos anteriores recebe o nome da propriedade que está sendo modificada, em forma de string. Isso é muito inconveniente, uma vez que o nome da propriedade pode vir a ser alterado posteriormente e podemos esquecer de alterar a string na chamada de RaisePropertyChanged. Com isso, a notificação não funcionaria como esperado.
Para simplificar esse método, podemos fazer uso do parâmetro opcional CallerMemberName no nosso método RaisePropertyChanged:
private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }
Com isso, não precisaríamos mais especificar os nomes das propriedades ao chamar o método RaisePropertyChanged:
public class Pessoa : System.ComponentModel.INotifyPropertyChanged { private string _nome; public string Nome { get { return _nome; } set { _nome = value; RaisePropertyChanged(); } } private string _sobrenome; public string Sobrenome { get { return _sobrenome; } set { _sobrenome = value; RaisePropertyChanged(); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } }
Viu como fica mais simples e menos suscetível a falhas?
Pois bem, espero que vocês tenham gostado de mais este artigo. Como sempre, você pode fazer o download do exemplo completo na MSDN Code Gallery.
Até o próximo artigo!
André Lima
Criando SplashScreens estendidas em Aplicações para a Windows Store Native Code Debugging – Uma opção perigosa
muito bom, bem explicado.
obrigado por compartilhar.
Olá!
Fico feliz em ouvir que você gostou do artigo! Muito obrigado pelo comentário!
André Lima