こんにちは。ゆとり(@yutori_techblog)です。
最近ReactiveExtensionsを使い始めたのですが、本当に便利ですね!
もうRxなしでコーディングは考えられません。
Observable.FromEventメソッドは使っていますか?
僕は訳あって使い始めたのですが、引数が複雑でよく分かりませんよね…。
最近やっと使い方を理解できたので、できるだけ分かりやすく紹介します!
- Observable.FromEventの使い方
- Observable.FromEventの3つの引数の意味
よくある使い方
Observable.FromEventの使い方を調べると、次のような例が出てきます。
ControlのClickイベントをIObservableに変換するコードですね。
public static IObservable<EventArgs> ObservableMouseClick(this Control control) =>
Observable.FromEvent<EventHandler, EventArgs>(
h => (s, e) => h(e),
h => control.Click += h,
h => control.Click -= h);
うん、意味がわからない。
僕は最初見たときラムダ式が連続で使われていることに目を疑いました。
なんだこの複雑さは…!!
解説
第二引数、第三引数:イベント登録
まずは簡単な第二引数と第三引数について説明します。
h => control.Click += h
h => control.Click -= h
これは見ての通りなのですが、eventにメソッドをadd、もしくはremoveするメソッドを指定します。
ではこれらのメソッドがいつ使われるかというと、それぞれ「Subscribeされたとき」、「Disposeされたとき」に呼ばれます。
例
第二引数、および第三引数のメソッド内に、それぞれ「イベントを追加しました」「イベントを削除しました」と出力するコードを追加しています。
その上で、SubscribeとDisposeを行ってみます。
using System;
using System.Reactive.Linq;
namespace ObservableTest
{
class Program
{
static void Main(string[] args)
{
//1秒毎にイベントを発行するクラスを生成
TestEventClass testClass = new TestEventClass();
//そのクラスのイベントをIObservableに変換
IObservable<EventArgs> ObservableTest =
Observable.FromEvent<EventHandler, EventArgs>(
h => (s, e) => h(e),
h =>
{
testClass.TestEvent += h;
Console.WriteLine("イベントを追加しました。");
},
h =>
{
testClass.TestEvent -= h;
Console.WriteLine("イベントを削除しました。");
});
Console.WriteLine("Subscribeします。");
IDisposable disposable = ObservableTest.Subscribe(_ => Console.WriteLine("TestEventが発行されました。"));
Console.ReadKey();
Console.WriteLine("Disposeします。");
disposable.Dispose();
}
}
/// <summary>
/// 1秒毎にイベントを発行するクラス
/// </summary>
class TestEventClass
{
public TestEventClass()
{
Observable.Interval(TimeSpan.FromSeconds(1))
.Subscribe(_ => TestEvent(this, EventArgs.Empty));
}
public event EventHandler TestEvent;
}
}

Subscribeした直後に、「イベントを追加しました。」が出力されていますね。
FromEventは、Subscribeされたときに第二引数に指定されたメソッドを呼び出していることがわかると思います。
同様にして、Disposeされたときに第三引数に指定されたメソッドを呼び出していることもわかりますね!
第一引数:シグネチャの変換
続いて、一番意味が分からないと思われる第一引数について説明します。
h => (s, e) => h(e)
分かりやすくカッコをつけるとこうなります。
h => ((s, e) => h(e))
このコード、何をやっているかというと、
Subscribeの引数に指定されたメソッドのシグネチャを、eventに登録できるような形に変換しています。
eventに直接メソッドを追加する場合、そのeventのシグネチャに合った形のメソッドを追加しなければなりませんでしたよね?
こんなふうに。
control.Click += (s, e) => MessageBox.Show("クリックされた");
引数がいらないからといって、シグネチャが異なるメソッドを追加するとエラーになります。
control.Click += () => MessageBox.Show("クリックされた");
//エラー!イベントにシグネチャの異なるメソッドを登録することはできない。
したがって、eventには基本的にシグネチャが同じメソッドしか登録できないのです。
しかし、Subscribeするときにeventのシグネチャをいちいち意識していたら面倒です。
こんなことしたくないですよね?
~.Subscribe((s, e) => MessageBox.Show("値を受け取った"));
いかにも「イベントです!」感があって嫌です。
そこで、使う側(Subscribeする側)が使いやすいように、メソッドの形を変換してやることができます。
では、どのように変換するかというと、もう一度先ほどのコードを見てみましょう。
h => ((s, e) => h(e))
Subscribeの引数に指定したメソッドは、hとして入ってきます。
それが、(s, e) => h(e)という新しいシグネチャを持つメソッドに変換されていることが分かりますでしょうか。
このことを踏まえた上で、FromEventの定義を見ると理解が深まると思います。
public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(Func<Action<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler);
第一引数は「Func<Action<TEventArgs>, TDelegate>型」です。
つまり、『「Action<TEventArgs>型」を引数にとり、「TDelegate型」を返すメソッド』を指定すれば良いわけですね。
では、TEventArgsとは何かというと、FromEventメソッド自体の戻り値IObservableの型引数なので、Subscribeしたときに流れてくる値の型ですよね。
「Action<TEventArgs>」は、EventArgs型を引数にとってvoid型を返すメソッドなので、Subscribeで指定するメソッドのシグネチャと同じであることが分かると思います。
つまり、
h => ((s, e) => h(e))
の「h」は、Subscribeで指定するメソッドだとわかると思います。
もう一度定義を見てみます。
public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(Func<Action<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler);
ここまでで、FromEventメソッドの第一引数に指定する「メソッド」の引数は、Subscribeしたときに流れてくる値の型と同じであることが分かりました。
では戻り値の「TDelegate」は何かというと、第二・第三引数で指定するメソッドの引数の型になっていますよね。
つまり、「イベントハンドラに登録できるようなシグネチャをもつメソッド」になります。
以上から、第一引数に指定するメソッドの役割は、「Subscribeに指定されたメソッドのシグネチャを、イベントに登録できるようなシグネチャに変換する」であることがわかると思います。
まとめ
以上が、わかりづらいObservable.FromEventメソッドの使い方説明でした。
まとめると次のようになります。
- 第一引数:Subscribeに指定されたメソッドを、eventのシグネチャに変換するためのコードを記述する。
- 第二引数:新しくSubscribeされたときにイベントにaddするためのコードを記述する。
- 第三引数:SubscribeがDisposeされたときに追加済みのイベントをremoveするためのコードを記述する。
以上です。お役に立てましたら幸いです。
ご意見・ご質問等ありましたらコメント、またはTwitterへお願いします。