Detectando mudanças em objetos

by Israel Aece 22. June 2010 12:14

Há uma porção de funcionalidades dentro do .NET Framework, que podemos incorporar em nossos tipos, para enriqucê-los ainda mais em nível de comportamento. Essas funcionalidades predefinidas, já trazem recursos extremamente interessantes, que com pouco de código extra que utilizamos para "rechear" os pontos customizados, poderemos poupar muitas e muitas linhas de código se fossemos fazer isso manualmente.

Entre as várias funcionalidades que existem e que já falei bastante por aqui, uma delas é a capacidade que temos de dectectar mudanças que acontecem no estado (propriedades) das nossas classes. Geralmente as classes possuem propriedades que podem ser alteradas durante a sua execução, sendo essa alteração realizada através do bloco Set da mesma, ou através de algum método que a manipula internamente.

Por algum motivo, se quisermos detectar que alguma mudança está acontecendo ou já aconteceu, podemos implementar nesta classe as interfaces INotifyPropertyChanging e INotifyPropertyChanged que estão debaixo do namespace System.ComponentModel. Cada uma delas é utilizada para interceptar momentos diferentes, ou seja, a primeira deve ser utilizada quando queremos ser notificados antes da mudança, enquanto a segunda deverá ser utilizada para notificar quando a mudança já aconteceu.

A primeira interface, INotifyPropertyChanging, fornece um único membro, que é o evento PropertyChanging. Já a segunda, INotifyPropertyChanged, disponibiliza um outro evento chamado PropertyChanged. Ambas interfaces podem ser implementadas em classes que você deseja monitorar as alterações que podem acontecer em suas respectivas propriedades. Com isso, podemos permitir aos consumidores desta classe, serem notificados antes e depois da mudança acontecer, podendo assim tomar alguma decisão em cima disso.

Como podemos notar no código abaixo, temos uma classe chamada Cliente, que por sua vez, contém apenas uma única propriedade chamada Nome. Para qualquer evento que você crie, o ideal é criar um método que encapsule a regra para a construção dos parâmetros e o disparo dele, para evitar redundâncias pelo código. Para isso, foram criados dois métodos auxiliares, onde cada um deles encapsula a chamada para o evento que ele gerencia. Note que a atribuição do valor ao membro interno da classe, somente se dá caso o valor que chega para ela seja diferente do qual ela possui, justamente porque não faz sentido notificar alguém que a propriedade foi mudada, mas que efetivamente não foi. É importante notar também que a alteração que será feita na propriedade está envolvida pela chamada dos eventos, ou seja, antes da alteração disparamos o evento PropertyChanging, e depois que a alteração foi realizada, disparamos o evento PropertyChanged.

public class Cliente : INotifyPropertyChanging, INotifyPropertyChanged
{
    private string _nome;

    public string Nome
    {
        get
        {
            return this._nome;
        }
        set
        {
            if (value != this._nome)
            {
                this.OnPropertyChanging("Nome");
                this._nome = value;
                this.OnPropertyChanged("Nome");
            }
        }
    }

    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanging(string propertyName)
    {
        if (this.PropertyChanging != null)
            this.PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Com isso, ao construir um objeto do tipo Cliente, definimos a sua propriedade Nome como "Israel". Em seguida, nos vinculamos aos eventos PropertyChanging e PropertyChanged, para sermos notificados caso qualquer alteração aconteça nas propriedades deste objeto. Tudo o que estamos fazendo no código abaixo, é escrevendo na tela a notificação da alteração da propriedade Nome. Finalmente, quando alteramos a propriedade Nome de "Israel" para "Israel Aece", notamos que as mensagens de notificação aparecerão na tela.

Cliente c = new Cliente() { Nome = "Israel" };

c.PropertyChanging += 
    (sender, e) => Console.WriteLine("Alterando a propriedade '{0}'.", e.PropertyName);

c.PropertyChanged +=
    (sender, e) => Console.WriteLine("Propriedade '{0}' alterada.", e.PropertyName);

c.Nome = "Israel Aece";

Assim como existe essas interfaces que agregam às nossas classes, a possibilidade de serem monitoradas quando alguma mudança acontecer, também há uma nova interface chamada INotifyCollectionChanged (namespace System.Collections.Specialized), que como o nome sugere, permite adicionar à uma coleção, a possibilidade de monitoramento da mesma, onde seremos notificados quando ela for modificada, ou seja, quando elementos forem adicionados ou removidos. Essa interface fornece um único membro, que é o evento CollectionChanged.

Esse evento faz uso de um parâmetro do tipo NotifyCollectionChangedEventArgs, que expõe algumas propriedades interessantes, como por exemplo: Action, NewItems e OldItems. O primeiro deles, retorna uma das opções do enumerador NotifyCollectionChangedAction, dizendo qual foi a ação que aconteceu. Já a propriedade NewsItems, disponiliza um objeto do tipo IList, contendo os novos itens que foram adicionados, enquanto a propriedade OldItens, retorna o mesmo tipo da propriedade anterior, mas com os objetos que foram removidos da coleção.

Essa interface nos permite criar uma coleção que pode ser monitorada quando os elementos dela são manipulados. Felizmente, a Microsoft já adicionou uma coleção genérica chamada ObservableCollection<T>, que está debaixo do namespace System.Collections.ObjectModel, já implementada com essa funcionalidade.

É importante dizer que quando estamos falando no monitoramento de modificações em uma coleção, estamos atentos aos itens que ela armazena, se novos são inseridos, se outros são removidos, alterados, etc. Se uma propriedade de um objeto que está dentro dela for alterada, nada acontecerá à coleção. Como exemplo de uso, podemos visualizar o código abaixo, que cria uma instância desta coleção, definindo o tipo T como sendo Cliente. Antes de começarmos a manipular a coleção, vamos nos vincular ao evento CollectionChanged, para sermos notificados quando novos itens forem adicionados ou removidos.

static void Main(string[] args)
{
    ObservableCollection<Cliente> clientes = new ObservableCollection<Cliente>();
    clientes.CollectionChanged += ColecaoAlterada;

    clientes.Add(new Cliente() { Nome = "Israel" });
    clientes.Add(new Cliente() { Nome = "Claudia" });
    clientes.Add(new Cliente() { Nome = "Virginia" });
}

static void ColecaoAlterada(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
        VisualizarClientes("Cliente(s) Adicionado(s)", e.NewItems);

    if (e.Action == NotifyCollectionChangedAction.Remove)
        VisualizarClientes("Cliente(s) Removido(s)", e.OldItems);
}

static void VisualizarClientes(string titulo, IList itens)
{
    if (itens != null && itens.Count > 0)
    {
        Console.WriteLine(titulo);

        foreach (var item in itens)
            Console.WriteLine("\t{0}", item);
    }
}

Ao executar o código acima, o método ColecaoAlterada será disparado três vezes, sendo uma para cada novo cliente que está sendo adicionado à coleção. Apesar de não estar sendo utilizado aqui, a classe ObservableCollection<T> também implementa a interface INotifyPropertyChanged, que tem a finalidade de monitorar duas propriedades, sendo elas: Items e Count, que são as principais propriedades de qualquer coleção.

Observação: Inicialmente os tipos que vimos aqui para monitoramento de coleções, foram criados para atender ao Windows Presentation Foundation, e justamente por isso, também podem ser encontrados dentro do assembly WindowsBase.dll. A partir da versão 4.0 do .NET Framework, a Microsoft trouxe esses tipos para dentro do assembly System.dll, que nos permite utilizá-los por qualquer tipo de aplicação, sem a necessidade de referenciar diretamente um assembly de uma tecnologia específica.

Conclusão: Vimos no decorrer deste artigo como podemos incorporar em nossas classes a funcionalidade para detectar mudanças, recorrendo à recursos do próprio .NET Framework. Grande parte do que vimos neste artigo, serve como base para grandes funcionalidades que estão espalhados por toda a plataforma .NET, como é o caso do databinding do WPF, consumo de serviços WCF (Data Services), entre outros. A ideia aqui foi apresentar, de uma forma "crua", essas funcionalidades, para mais tarde abordar outros recursos expostos pelo .NET Framework que fazem uso disso, e que já assumirão o conhecimento que vimos aqui.

Tags: ,

.NET Framework | C#

MultiMethods

by Israel Aece 1. May 2009 20:03

É muito comum criarmos métodos em nossas classes que possuam “versões” diferentes. Por “versão”, entenda como overload ou sobrecarga do método. Isso quer dizer que podemos ter um método com o mesmo nome, mas que possuem quantidade ou tipos diferentes.

Em linguagens como o C# e o VB.NET, a resolução de qual dos overloads será invocado é feita em tempo de compilação, baseando no tipo que está sendo passado para o método, o compilador determinará qual das versões invocar. Suponhamos que temos a seguinte estrutura de classes:

public class Pessoa
{
    public string Nome;
}

public class PessoaJuridica : Pessoa
{
    public string CNPJ;
}

public class PessoaFisica : Pessoa
{
    public string CPF;
}

Agora, temos uma classe que tem a finalidade de gerenciar instâncias da classe Pessoa. Essa classe possui dois overloads, onde cada um deles espera uma instância concreta da classe Pessoa:

public class GestorDePessoas
{
    public void AdicionarPessoa(PessoaFisica pf)
    {
        Console.WriteLine("PF");
    }

    public void AdicionarPessoa(PessoaJuridica pj)
    {
        Console.WriteLine("PJ");
    }
}

Nossa aplicação trabalhará de forma independente de qual tipo de Pessoa esteja utilizando. Haverá um método chamado ConstruirPessoa que, dado o nome e o número do documento, retornará uma instância da classe PessoaFisica ou PessoaJuridica que, no nosso caso, deveremos passá-la para a classe GestorDePessoas. Se traduzirmos este parágrafo para código, então teríamos algo como:

Pessoa p = ConstruirPessoa(“Israel Aece”, “123456789”);
GestorDePessoas gp = new GestorDePessoas();
gp.AdicionarPessoa(p);

Se tentarmos compilar, receberemos um erro avisando que não foi possível encontrar um método com essa assinatura. As duas versões do método AdicionarPessoa esperam instâncias das classes concretas (PessoaFisica e PessoaJuridica). Mesmo que “p” armazene internamente a instância de uma classe concreta, ele não conseguirá determinar qual dos métodos invocar, pois somente saberemos o que “p” representa durante a execução da aplicação.

MultiMethods é um conceito que existe para escolher qual dos métodos invocar em tempo de execução, ao contrário do que acontece com o overloading, que fará essa escolha estaticamente, ainda em tempo de compilação. Há várias implementações de MultiMethods para .NET, e uma delas é o MultiMethods.NET. Com essa library, podemos criar apenas uma versão pública do método, que por sua vez, receberá a versão mais básica do tipo, que no nosso caso é a classe Pessoa. Internamente, a library determinará qual dos overloads invocar de acordo com o tipo que está sendo passado. O código abaixo ilustra a classe GestorDePessoas ligeiramente alterada, utilizando a library em questão:

public class GestorDePessoas
{
    private MultiMethod.Action<Pessoa> _adicionar;

    public GestorDePessoas()
    {
        this._adicionar = Dispatcher.Action<Pessoa>(this.AdicionarPessoa);
    }

    public void AdicionarPessoa(Pessoa p)
    {
        this._adicionar(p);
    }

    protected virtual void AdicionarPessoa(PessoaFisica pf)
    {
        Console.WriteLine("PF");
    }

    protected virtual void AdicionarPessoa(PessoaJuridica pj)
    {
        Console.WriteLine("PJ");
    }
}

O código de consumo desta classe que antes não funcionava, passa a funcionar, e em tempo de execução, o método AdicionarPessoa será invocado de acordo com o tipo retornado pelo método ConstruirPessoa. Publicamente temos apenas o método AdicionarPessoa que aceita o tipo base, e dentro dele, utiliza o delegate fornecido pela library para efetuar a escolha do método.

É importante dizer que estas libraries de MultiMethods recorrem a Reflection para efetuar todo o trabalho, e como sabemos, isso possui um overhead extra. Se de um lado ganhamos em flexibilidade, do outro perdemos em performance. Já com o .NET 4.0, o “tipo” dynamic evitará o uso dos MultiMethods. Esta palavra chave informará ao compilador que o método somente deverá ser conhecido em tempo de compilação, também se baseando nos tipos que forem passados à ele.

Ao contrário de grande parte dos tipos do .NET, a keyword dynamic não esta mapeada para algo como System.Dynamic; ao detectar uma variável definida com esta keyword, o compilador efetivamente criará uma variável do tipo System.Object mas, internamente, deverá recorrer à alguma API de DLR para invocar o membro, que talvez possa ser mais performático que Reflection.

Tags: , ,

C# | VB.NET

Tuples

by Israel Aece 27. April 2009 23:55

Especulando o .NET Framework 4.0 no Reflector, eu vi que há uma estrutura de Tuples. Tuples é uma forma que temos para representar algum dado, ou um conjunto deles, sem necessariamente ter uma classe/estrutura por trás disso. Tudo na programação orientada à objetos e nas linguagens fortemente tipada, temos que criar fisicamente a classe e sua estrutura de propriedades para, em seguida, poder utilizá-la.

As Tuples tem um papel importante na programação dinâmica, e como sabemos que C# está cada vez mais ganhando alguns conceitos deste tipo, as Tuples trazem uma importante característica para o nosso código. Muitas vezes já nos deparamos com um cenário onde precisamos retornar mais do que um resultado. Neste caso, utilizamos o retorno tradicional do método em conjunto com alguns parâmetros de saída (out/ref). Apesar de conseguir contornar com a criação de tipos (classes) adicionais, as Tuples vão muito além, já que não há a necessidade da criação deste tipo.

Os tipos anônimos tem uma funcionalidade semelhante, mas tem um escopo limitado, podendo ser acessado somente dentro do bloco onde foi criado. As Tuples vão mais além, sendo possível definí-las como retorno de métodos, propagando para outros lugares.

As Tuples são imutáveis, com tamanho fixo e permite o acesso diretamente a cada um dos valores através de propriedades “dinamicamente criadas”. Assim como os tipos anônimos e variáveis declaradas com o “tipo” var, a tipificação será determinada através da atribuição do(s) valor(es) à Tuple. Notem que nos exemplos abaixo, não especificamos em nenhum momento os tipos que estamos trabalhando:

var tuple = Tuple.Create(1, “Israel Aece”); 
var id = tuple.Item1; 
var nome = tuple.Item2;

//ou algo assim:

var tuple = AlgumaFuncaoQueRetornaMaisDeUmValor();
var id = tuple.Item1; 
var nome = tuple.Item2;

Infelizmente, na versão atual do CTP do .NET 4.0, as classes que representam as Tuples estão definidas como internal. Essas classes estão sendo criadas para melhorar a interoperabilidade com o F#, mas espero que elas se tornem públicas até a versão final, para que possamos fazer uso em nossas aplicações.

Tags: ,

.NET Framework

Partial Methods

by Israel Aece 2. June 2007 09:24

Teremos Partial Methods também no VB.NET!

Tags:

VB.NET

Closures em VB.NET

by Israel Aece 25. May 2007 19:46

Uma seqüencia legal sobre closures que estão sendo implementadas dentro do VB.NET 9.0 (Orcas):

Tags:

VB.NET

Partial Methods

by Israel Aece 24. May 2007 19:47

Uma funcionalidade um tanto quanto interessante...

Tags:

C#

A evolução dos Delegates

by Israel Aece 9. May 2007 19:50

Em tão pouco tempo o delegate evoluiu muito dentro da plataforma .NET. Supondo que temos um delegate que recebe dois números inteiros e retorna um valor, também inteiro, temos as seguintes formas de proceder para executar o delegate nas respectivas versões do .NET até o momento:

delegate int Operacao(int a, int b);

[ Versão 1.x ]
static void Main(string[] args)
{
    Operacao op = new Operacao(Soma);
    Console.WriteLine(op(2, 4));
}

public static int Soma(int a, int b)
{
    return (a + b) * 3;
}

[ Versão 2.0 ]
static void Main(string[] args)
{
    Operacao op = new Operacao(delegate(int a, int b)
    {
        return (a + b) * 3;
    });

    Console.WriteLine(op(2, 4));
}

[ Versão 3.5 ]
static void Main(string[] args)
{
    Operacao op = (a, b) => (a + b) * 3;
    Console.WriteLine(op(2, 4));
}

A primeira versão não tem o muito o que comentar. É aquilo e pronto! Já a versão 2.0 do .NET permitiu a criação de métodos anonimos, o que evita de criarmos um método auxiliar (Soma) para vincularmos ao delegate. Já a versão 3.5 vai além. Como podemos notar, especificamos os parametros antes do operador "=>" (e os tipos são inferidos de acordo com a assinatura do delegate). Após esse operador é onde efetivamente colocaremos o código do método.

Isso é usado extensivamente quando utilizamos o Linq. Particularmente, a primeira vista eu também achei um bocado complexo mas praticando (nem que for por brincandeira) voce se adapta rapidamente.

Tags: , ,

.NET Framework | C#

Extension Methods em VB.NET

by Israel Aece 6. May 2007 19:52

Semana passada um aluno me perguntou como ele deveria proceder para criar um Extesion Method no Visual Basic .NET (Orcas). Ao contrário do C#, o Visual Basic .NET 9.0 necessita que o método estático a ser uma extensão de um determinado tipo, seja decorado com um atributo chamado ExtensionAttribute, que está contido dentro do namespace System.Runtime.CompilerServices (Assembly System.Core.dll). O exemplo abaixo mostra a sua utilização:

Imports System.Runtime.CompilerServices

Module Helper
    <Extension()> _
    Public Function RecuperaCincoPrimeiros(ByVal value As String) As String
        Return value.Substring(0, 5)
    End Function
End Module

Module Module1
    Sub Main()
        Dim valor As String = "1234567890"
        Console.WriteLine(valor.RecuperaCincoPrimeiros())
    End Sub
End Module


A única coisa que não gostei é que todo extesion method deve, obrigatoriamente, ser definido dentro de um módulo. Não sei porque a Microsoft não permite a criação de extension method dentro de uma classe, pois um módulo, depois de compilado, não deixa de ser uma classe concreta (NotInheritable), com todos os membros estáticos (Shared).

Na verdade, custa-me a acreditar que um módulo do VB.NET é equivalente a uma classe estática do C#. Imaginei que a Microsoft iria manter os módulos apenas por uma questão de compatibilidade com o VB6, mas está indo muito além disso...

Tags:

VB.NET

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host