イベント・基本パターン - C#

イベントの基本的な作成例を書いてみます。

まずはコードから。

using System;
using System.ComponentModel;

namespace EventSample
{
    class Fuga
    {
        #region Event
        //using System.ComponentModel;が必要
        protected EventHandlerList Events = new EventHandlerList();

        private static readonly object EventValueChanged = new object();

        public delegate void ValueChangedEventHandler(object sender, EventArgs e);

        public event ValueChangedEventHandler ValueChanged
        {
            add { Events.AddHandler(EventValueChanged, value); }
            remove { Events.RemoveHandler(EventValueChanged, value); }
        }

        protected void RaiseEvent(object key, EventArgs e)
        {
            Delegate handler = Events[key];

            if (handler != null)
            {
                handler.DynamicInvoke(new object[] { this, e });
            }
        }

        protected virtual void OnValueChanged(EventArgs e)
        {
            RaiseEvent(EventValueChanged, e);
        }
        #endregion


        #region Constructor
        public Fuga()
        {
            _value = 0;
        }
        #endregion


        #region Property
        int _value;
        public int Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (this.Value != value)
                {
                    _value = value;
                    this.OnValueChanged(new EventArgs());
                }
            }
        }
        #endregion

    }


    class Program
    {
        static void Main(string[] args)
        {
            Fuga fuga = new Fuga();

            //フックする
            fuga.ValueChanged +=new Fuga.ValueChangedEventHandler(fuga_ValueChanged);

            fuga.Value = 1;

            //or ValueChangedイベントを意図的に発生させるなら
            //以下をコメントアウトして下さい。
            //fuga.OnValueChanged(new EventArgs());
        }

        //イベントが発生すると行われるメソッド
        private static void fuga_ValueChanged(object sender, EventArgs e)
        {
            Console.WriteLine("Value Changed.");
        }
    }
}


イベントハンドラの入れ物 - Events

protected EventHandlerList Events = new EventHandlerList();
『EventHandlerList』と言う名のとおり、イベントハンドラをこの"Events"に格納します。
System.ComponentModelを継承しているオブジェクトであれば、既にあるはずなので作成する必要はありません。
例えば、新規Windowsアプリケーションを作成し、デフォルトで作成されるフォームのソースに"Events"と入力すればインテリセンス機能で以下のように表示されます。
これはフォームがSystem.ComponentModelを継承しているからです。


イベントハンドラを取り出すには以下のようにします。
Delegate handler = Events[key];
handlerに格納したイベントハンドラはマルチキャストデリゲートですので、対象のイベントに複数イベントハンドラがフックされていてもDelegate[]とはなりません。
このように、Eventsからイベントハンドラを取り出すには一意であるキーとなるオブジェクトをインデックスに指定する必要があります。

キーの作成

キーの作成例)
private static readonly object EventValueChanged = new object();
このキーそのものに何モノかを代入したりして使用することはありません。
既存のコンポーネントの場合、キーの命名にはある程度の規則性があります。
Event + イベント名
独自に作成する場合にも、Reflectionを使用してこのイベントハンドラを取得する場合を考慮すると、その規則に出来るだけ合わせることが望ましいのでは、と考えます。

イベントハンドラの型を定義する

イベントハンドラの型がそのままイベントの型になります。つまり対象のイベントにフックできるメソッドの型をイベント宣言時にイベントの型として使用します。
public delegate void ValueChangedEventHandler(object sender, EventArgs e);
しかし、不必要に型を定義しなくとも通常はSystem.EventHandlerを使用すれば良いでしょう。
例) public event EventHandler ValueChanged

イベントのアクセサ

イベントもアクセサを作成することで実際の内部での出し入れを制御します。
このアクセサを通してEventsへの出し入れを行います。
public event ValueChangedEventHandler ValueChanged
{
    add { Events.AddHandler(EventValueChanged, value); }
    remove { Events.RemoveHandler(EventValueChanged, value); }
}

RaiseEventメソッドの作成

このメソッドの作成は効率化だけでなく、統一化も含まれています。
protected void RaiseEvent(object key, EventArgs e)
{
    Delegate handler = Events[key];

        if (handler != null)
        {
            handler.DynamicInvoke(new object[] { this, e });
        }
}
引数のEventArgsの型やsenderの指定など、イベントの種類によって作成し、使い分けるのが良いでしょう。

例えば、フォームのRaiseEventメソッドには以下のものがあります。
protected void RaiseDragEvent(object key, DragEventArgs e);
protected void RaiseKeyEvent(object key, KeyEventArgs e);
protected void RaiseMouseEvent(object key, MouseEventArgs e);
protected void RaisePaintEvent(object key, PaintEventArgs e);

イベントを発生させるメソッドの作成

イベントによっては意図的にそのイベント発生させることが必要な場合があります。そのためのメソッドを作成します。アクセス修飾子はもちろんpublicです。
public virtual void OnValueChanged(EventArgs e)
{
    RaiseEvent(EventValueChanged, e);
}
一般的にメソッド名は「On + イベント名」とします。

このメソッドの用途はこれだけではありません。
上記のコードで実際にこのメソッドが使われている箇所を見てみます。
public int Value
{
     get
    {
        return _value;
    }
    set
    {
        if (this.Value != value)
        {
             this._value = value;
            this.OnValueChanged(new EventArgs());
        }
    }
}

もし、効率を考えるなら以下のようにするのが妥当です。
set
{
    if (this.Value != value)
    {
        this._value = value;
        if (Events[EventValueChanged] != null)
        {
            Events[EventValueChanged].DynamicInvoke(new object[] { this, new EventArgs() });
        }
    }
}
ワザワザ回りくどくイベントハンドラを呼び出しているのは、理由があります。
もし、継承したクラスでValueChangedイベントを発生させたくない場合、以下のようにすることでイベントの発生を防ぐことができます。
class FugaFuga : Fuga
{
    protected override void OnValueChanged(EventArgs e) { }
}
上記はFugaFugaクラスがFugaクラスを継承し、OnValueChangedメソッドをオーバーライドしています。
メソッド内に敢えて何も記述しないことでValueChangedイベントが発生しても何も起こらなくしています。
このようなことを可能にするために、イベントを起こすメソッドにはvirtualを指定します。
public virtual void OnValueChanged(EventArgs e)

0 Comments:

Recent Posts