Moss’s blog

すごくなるためのブログ

Observable.FromEventについて調べてみた

最近、ReactiveExtensionsを勉強しています。(前回の投稿からかなり飛んでいる。。。) 調べながら進めていますが、ファクトリメソッドEventFromがわかりにくい。 丸覚えでもいいのですが、もう少し理解したいと思い調べてみました。(今更の記事感がありますが)

Observable.FromEventについて

まずは基本。シグネチャを定義から引用します。

IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
Func<Action<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, 
Action<TDelegate> removeHandler);

わかりにくい。。(私だけ?)

定型のコール

Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
    h => (s, e) => h(e),
    h => button.Click += h,
    h => button.Click -= h
    ).Subscribe(e => textBlock.Text = string.Format("{0}: Ckicked",DateTime.Now));

ボタンクリックイベントを変換する処理。ボタンクリックされるとtextBlockのTextプロパティに日時を出力するサンプルです。

わかりにくい点

ここでわかりにくい点を挙げてみます。

  1. 第一引数は、=>が二つもあり複雑でわからない。
  2. サンプルすべての引数に「h」という変数が使われており、関連がかわらない。

わかりやすく書き換え

上記の点を踏まえて、わかりやすく書き換えてみます。

Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
    handler1 => 
    {
        RoutedEventHandler action = (sender, e) =>
        {
            handler1(e);
        };
        var conversion = new RoutedEventHandler(action);
        return conversion;
    },
    handler2 => button.Click += handler2,   // 第一引数の戻り値
    handler2 => button.Click -= handler2     // 第一引数の戻り値
    ).Subscribe(e => textBlock.Text = string.Format("{0}: Ckicked", DateTime.Now));

以下の点が見えてきました。

  1. 第一引数にある2つの=>は、handlerA(key)を返却する(return)処理
  2. 第一引数のhと、第二第三引数のhは別物
  3. そしてhandlerAとは、Subscribeに渡すメソッドのこと。

処理の流れ

処理の流れをまとめてみま。画面からボタンをクリックしたとき以下の処理が実行されます。

  1. KeyPressが発行される。
  2. handlerBが実行される。
  3. handlerBが参照しているAction「handlerA(key)」が実行される。
  4. handlerAとは、Subscribeの引数で渡されるメソッド(value => Console.WriteLine("OnNext({0})", value))

シグネチャを改めて見てみる

IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
Func<Action<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, 
Action<TDelegate> removeHandler);

第一型引数: TDelegate

  • 購読するイベントのシグネチャ
  • Rx世界への変換前の型(イベント)

第二型引数: TEventArgs

  • Observerへ渡る引数の型
  • Rx世界への変換後の型(ストリームに流す型)

まとめ

  • FromEventは.NETのイベントをRxの世界に流すことのできる素敵なファクトリメソッド。
  • 第一型引数には、イベントシグネチャを指定します。
  • 第二型引数には、ストリームに流すデータの型を指定します。

省略記法が多用されているので、わかる人にはわかりやすいのだろうけど初めての人にはとっつきにくいものですね。 (私の慣れが足りていないということかな) 最後に全コードを記載しておきます。

using System;
using System.Reactive.Linq;
using System.Windows;

namespace RxStudyWpf
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
            //    h => (s, e) => h(e),
            //    h => button.Click += h,
            //    h => button.Click -= h
            //    ).Subscribe(e => textBlock.Text = string.Format("{0}: Ckicked",DateTime.Now));


            Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
                handler1 => 
                {
                    RoutedEventHandler action = (sender, e) =>
                    {
                        handler1(e);
                    };
                    var conversion = new RoutedEventHandler(action);
                    return conversion;
                },
                handler2 => button.Click += handler2,
                handler2 => button.Click -= handler2
                ).Subscribe(e => textBlock.Text = string.Format("{0}: Ckicked", DateTime.Now));
        }
    }
}
<Window x:Class="RxStudyWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="200">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Name="button" Content="Click"></Button>
        <TextBlock Grid.Row="1" Name="textBlock"></TextBlock>
    </Grid>
</Window>

参考:

blog.xin9le.net