Reactive Extensions (Rx)–Linq to Events

Posted on 01/09/2010

10


Olá galera, tudo certin? Depois de um passeio pelo mundo da Intermediate Language, resolvi voltar a abordar assuntos mais “altos”. Hoje, quero falar um pouquinho sobre Reactive Extensions (mais conhecido por Rx), ou ainda, Linq to Events.

Antes de qualquer coisa, meu reconhecimento a Erik Meijer. Para quem não sabe quem é o cara, ele é o “pai” do Linq e do Rx dentro da Microsoft. Recomendo fortemente qualquer vídeo com ele no Channel9.

Importante: Esse post não tem a proposta de apresentar fundamentos. Vou aguardar o feedback da comunidade para iniciar uma série 101 para esse assunto.

O que é o Reactive Extensions?

A página oficial do produto define Rx como:

  • uma biblioteca para composição assíncrona de aplicações baseadas em eventos usando coleções observáveis;
  • uma conjunto extendido  de operadores LINQ que possibilita computação assíncrona/baseada em eventos como “push-based”, através das novas interfaces do .NET 4 IObservable e IObserver.

Entendeu?! Não!? Nem eu quando li pela primeira vez. Por isso, esse artigo é todo baseado em exemplos Smiley piscando

Importante: Antes de continuar, faça o download da extensão de bibliotecas na página do Reactive Extensions

Eu acredito na programação declarativa

Para quem chegou de marte, temos duas formas de programar um computador. Podemos ser:

  1. imperativos – dizendo ao computador o que queremos que ele faça, e como queremos que ele faça. abordagem tradicional com For/If/While;
  2. declarativos – dizendo ao computador o que queremos que ele faça e deixando que ele escolha como fazer. fazemos isso há algum tempo usando SQL e afins.

Há uma certa tendência, ao meu ver, de migrar as tecnologias de desenvolvimento para a “forma declarativa”. Rx é um passo gigante nessa direção.

Nosso aplicativo de testes

Para ilustrar Rx, usarei uma aplicação base. Para criá-la:

  1. Inicie um novo Windows Console no VS2010;
  2. Adicione referências para System.Windows.Forms e System.Drawing;
  3. Adicione referências para System.CoreEx e System.Reactive. (Lembre-se de instalar as extensões que estão disponíveis no site oficial do Reactive Extensions antes de começar; Lembre-se também de mudar o Target Framework para .NET Framework 4)
  4. Substitua o código de Program.cs por esse:

              
using System; using System.Linq; using System.Windows.Forms; namespace Rx1 { class Program { static void Main(string[] args) { var form = new Form(); Application.Run(form); } } }

              

Um exemplo simples

Para começar, uma tarefa trivial… Queremos imprimir no console as coordenadas do ponteiro enquanto ele estiver sobre a janela.

Na forma tradicional, escreveríamos algo assim:


              
static void Main(string[] args) { var form = new Form(); form.MouseMove += (s, e) => Console.WriteLine(e.Location.ToString()); Application.Run(form); }

              

Simples . bem simples .. Mas nem por isso bom!! Vejamos o porquê:

  1. Eventos são fontes de dados ocultas. Precisamos, obrigatóriamente de um handler para ter acesso aos dados.
  2. Eventos sempre são submetidos ao tratamento do handler (se quiséssemos restringir o domínio de pontos tratados pelo código do handler, deveríamos colocar um código de aceite logo no início);
  3. Eventos não suportam composição (pelo menos, não de forma fácil). Se desejássemos encaminhar os pontos recebidos pelo nosso evento, deveríamos colocar uma chamada explícita para o método de seqüência no corpo de código do handler;
  4. Eventos requerem manutenção manual de assinatura (Quantos objetos ficam pendurados na memória por assinarem eventos e não cancelarem a assinatura).

Agora, a forma Rx para o código acima:


              
static void Main(string[] args) { var form = new Form(); var mouseMove = Observable .FromEvent<MouseEventArgs>(form, "MouseMove"); using (var s = mouseMove.Subscribe((e) => Console.WriteLine(e.EventArgs.Location.ToString()))) { Application.Run(form); } }

              

Diferente.. parece até mais complexo .. Mas melhor!! Vejamos o porquê:

  1. Tiramos o evento de cena! Agora, estamos operando sobre uma coleção de pontos.
  2. Embora estejamos sempre executando uma Action, ela está vinculada a “coleção” de pontos observáveis, que pode ser filtrada via LINQ (próximos exemplos)
  3. Nossa varíavel mouseDown suporta composição. Ou seja, posso usar todos os operadores LINQ para processar os elementos que “saem” de mouseDown;
  4. Tenho mais facilidade em fazer o CleanUp, toda inscrição tem um dispose que “desassina” automaticamente o evento.

O que quero dizer com usar operadores LINQ?

Exatamente isso. Veja o código que segue:


              
static void Main(string[] args) { var form = new Form(); var mouseMove = Observable .FromEvent<MouseEventArgs>(form, "MouseMove"); var pts = from a in mouseMove where a.EventArgs.Location.X > 50 select a.EventArgs.Location; using (var s = pts.Subscribe((pt) => Console.WriteLine(pt.ToString()))) { Application.Run(form); } }

              

Olha lá o Rx mostrando sua força! Observe que eu não precisei adicionar uma condição no tratador. Antes de tudo, eu “declarei” que pontos com X < 50 não deveriam ser considerados. Além disso, observe que eu simplifiquei o tratamento: Se apenas a localização é importante para meu Handler, por que eu estava passando TODO o args…

Legal, né? Smiley piscando

Empilhando Querys aos Eventos

Como você faria para implementar uma operação de Drag usando Windows Forms? Seguramente, você precisaria assinar os eventos MouseDown, MouseMove e MouseUp. Além disso, precisaria manter um registro do ponto onde ocorreu o MouseDown (variável de classe). Mais ainda, precisaria manter um flag que indicasse se estaria ou não ocorrendo um drag. Certo? Sim, antes do Rx..

O código a seguir mostra como imprimir as coordenadas do ponteiro enquanto o botão do mouse permanecer pressionado (base para dragging):


              
static void Main(string[] args) { var form = new Form(); var mouseDown = Observable .FromEvent<MouseEventArgs>(form, "MouseDown"); var mouseUp = Observable .FromEvent<MouseEventArgs>(form, "MouseUp"); var mouseMove = Observable .FromEvent<MouseEventArgs>(form, "MouseMove"); var pts = from down in mouseDown from move in mouseMove.TakeUntil(mouseUp) select move.EventArgs.Location; using (var s = pts.Subscribe((pt) => Console.WriteLine(pt.ToString()))) { Application.Run(form); } }

              

Simplesmente, lindo! Smiley de boca aberta

Por fim, um exemplo matador

Combinando Rx com um pouquinho de tipos anônimos…


              
static void Main(string[] args) { var form = new Form(); var mouseDown = Observable .FromEvent<MouseEventArgs>(form, "MouseDown"); var mouseUp = Observable .FromEvent<MouseEventArgs>(form, "MouseUp"); var mouseMove = Observable .FromEvent<MouseEventArgs>(form, "MouseMove"); var pts = from down in mouseDown from move in mouseMove.TakeUntil(mouseUp) select new { start = down.EventArgs.Location, finish = move.EventArgs.Location }; using (var s = pts.Subscribe( data => { Console.WriteLine(data.ToString()); using (Graphics g = form.CreateGraphics()) g.DrawLine(Pens.Red, data.start, data.finish); } )) { Application.Run(form); } }

              

podemos fazer isso:

image

 

Por hoje, é isso?!

Etiquetado:, ,
Posted in: Post