イベントを理解する - C#

「リフレクションでイベントハンドラを取得したい。」
では「イベントハンドラを取得するために必要なことは何か?」を考え、そのためにはイベントの仕組みを理解する必要があるので今一度イベントの仕組みをここで確認したい。

以下に掲載されたコードを例に話を進めます。


オブジェクト(コントロール等)がイベントに登録されたイベントハンドラをどのようにして保持しているか?

既存のオブジェクト(コントロール等)であればSystem.ComponentModel名前空間のComponentクラスから派生し、このComponentクラスがイベントハンドラを格納するEventHandlerList型の"Events"というプロパティを持っている。

[F12]キーを押すと定義を見ることが出来ます。こんな感じで
protected EventHandlerList Events { get; }
既存のコントロールのイベントハンドラは通常この"Events"に格納される。全てがこれに格納されるわけではない。コントロールによっては用途によって複数に分けて格納している。(例えばフォームなど)
protectedなのが気になるが…


EventHandlerListはどのように値を保持するのか?

以下の通り。
public Delegate this[object key] { get; set; }
つまり、EventHandlerListはDelegate型のインデクサでobject型のインデックスを使用する。
連想配列に似た方法でキーを文字列や数値ではなくオブジェクトとしているところがポイントでしょうか。
イベントハンドラの追加/削除には、通常それ用に公開された以下のメソッドを使用して行う。
public void AddHandler(object key, Delegate value);
public void RemoveHandler(object key, Delegate value);


マルチキャスト デリゲートは += でメソッドを幾つもブチ込んで実行できる。
つまり、メソッドのポインタを格納するListをイメージすればイイ。
Events[key]によって取得するデリゲート型のイベントハンドラはたとえ複数であったとしても配列ではない


Eventsに格納されたイベントハンドラを取得するには以下のように行う。
Delegate handler = Events[key];


個々のデリゲートオブジェクトを取得するには、GetInvocationListを使用する。
Delegate[] h = handler.GetInvocationList();
イベントハンドラをDelegate型の配列として取得している。


取得したイベントハンドラを実行するには、DynamicInvokeを使用する。
handler.DynamicInvoke(new object[] {this, e });


実際にはこのように使用することになる。
protected void RaiseEvent(object key, EventArgs e)
{
    Delegate handler = Events[key];
    if (handler != null)
    {
        handler.DynamicInvoke(new object[] { this, e });
    }
}


まとめ

イベントハンドラを取得するには
  • イベントハンドラを格納する変数(上記例ではEvents)
  • そのイベントハンドラを取りだすkeyとなるオブジェクト(mouseDownEventKeyなど)
この二つを取得する必要がある、ということになる。

私の知る限りでは、既存のオブジェクトで使用されているkeyの名前には全てではありませんが『"Event" + イベント名』という規則性が当てはまることが多い。

しかし、どちらも既存のオブジェクトを対象とするなら、変数名は一定の規則に従って名前付けが行われているだろうが、サードパーティ製のモノとなればこれらを取得するのは困難かもしれない。

OLE DBConnectionとSQLConection - C#

OLE DBと言えば、

データベースの種類によらず統一的な手法でデータベースにアクセスするためのプログラミングインターフェース...
引用:IT用語辞典 
要はデータベースの種類が変わっても基本的には同じ方法でプログラミングできるということなんですが、では...
データベースは同じでも異なる接続でデータベースとやり取りすると書き方は変わってしまうのだろうか?

Sample Code

using System;
using System.Data;
using System.Data.OleDb;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Data.Common;

namespace DataBaseConnect
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //1. コネクションの作成と設定を行う
            //コネクションインスタンスを生成する
            using (OleDbConnection connection = new OleDbConnection())
            {
                //接続文字列を指定
                connection.ConnectionString = GetOLEDBConnectionString();


                //2. データアダプタの作成と設定を行う
                //データアダプタのインスタンスを生成する
                OleDbDataAdapter dataAdapter = new OleDbDataAdapter();

                //データを取得するためのSQL文を作成し、指定する
                OleDbCommand command = new OleDbCommand("SELECT * FROM PRODUCTS");
                dataAdapter.SelectCommand = command;

                //データアダプタのコネクションの設定
                dataAdapter.SelectCommand.Connection = connection;

                //DataTableMappingの作成とデータアダプタのTableMappingの指定
                DataTableMapping mapping = new DataTableMapping("Table", "Products");
                dataAdapter.TableMappings.Add(mapping);


                //データセットの生成
                DataSet dataSet = new DataSet();


                // 4. データセットにデータを取得する
                dataAdapter.Fill(dataSet);


                // 5. データセットからテーブルを取得する
                DataTable dataTable = dataSet.Tables["Products"];


                // 6. DataGridViewにデータを表示する
                dataGridView1.DataSource = dataTable;
            }
        }






        private void button2_Click(object sender, EventArgs e)
        {
            using (SqlConnection connection = new SqlConnection())
            {
                connection.ConnectionString = GetSQLConnectionString();

                SqlDataAdapter dataAdapter = new SqlDataAdapter();

                SqlCommand command = new SqlCommand("SELECT * FROM PRODUCTS");
                dataAdapter.SelectCommand = command;

                dataAdapter.SelectCommand.Connection = connection;

                DataTableMapping mapping = new DataTableMapping("Table", "Products");
                dataAdapter.TableMappings.Add(mapping);

                DataSet dataSet = new DataSet();
                dataAdapter.Fill(dataSet);

                DataTable dt = dataSet.Tables["Products"];
                dataGridView1.DataSource = dt;
            }
        }




        private static string GetOLEDBConnectionString()
        {
            return
                "Provider=SQLOLEDB;" +
                @"Data Source=.\SQLEXPRESS;" +
                @"Initial Catalog=|DataDirectory|\ NORTHWND.mdf;" +
                "Integrated Security = SSPI;";
        }



        private string GetSQLConnectionString()
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource = @".\SQLEXPRESS";
            builder.AttachDBFilename = @"|DataDirectory|\NORTHWND.mdf";
            builder.IntegratedSecurity = true;
            builder.UserInstance = true;

            return builder.ConnectionString;
        }
    }
}

つまりやっていることほぼ同じ。

SQL Serverのデータベースを扱うのにわざわざOLEDB何ぞ使わずともほぼ同じ方法(処理)で出来ちゃうわけですね。

つまり、SQLConnectionとOLEDBConnectionとで大きな違いがないなら、ワザワザ分ける意味は何処に...

そもそもOLE DBでSQL Severのデータベースを扱えるメリットが分からない、ということでした。

イベント定義 - C#

イベントを起こすには、

  • イベントハンドラの型を宣言する
  • イベントを定義する
  • イベントハンドラであるメソッドを作成する
  • イベントにイベントハンドラを登録する
以上の四つが必要ですが、今回は主に『イベントハンドラの型宣言』と『イベント定義』について。


簡単にイベントを作成するには既存のイベントハンドラの型を利用する。

System 名前空間にある EventHandler を利用する

何もしなくともSystem.EventHandlerは使えるはずなのでそれを使って
例えば、こんな感じでイベントを定義する
public event EventHandler fuga;
イベントハンドラの型宣言がいらないのでラク。

ComponentModel 名前空間にある INotifyPropertyChanged を利用する

usingディレクティブにSystem.ComponentModelを追加するとPropertyChangedEventHandler を利用できるようになる。

こんな感じでイベント定義する
public event PropertyChangedEventHandler PropertyChanged;

要するにこの一行を書かなくてすむ。
public PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

PropertyChanged イベントが発生したら行われるメソッドを追加するには以下のように。
this.PropertyChanged += new PropertyChangedEventHandler(hoge_PropertyChanged);

PropertyChanged イベントを発生させるメソッドの形がこんな感じ。
public void OnPropertyChanged(string pname)
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(pname));
    }
}

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

基本的にデリゲート型なのでデリゲートを理解していれば簡単なので、デリゲートを理解していないならまずはそちらから学ぶべき。

はっきり言うと独自に作成したイベントハンドラの型が結局System.EventHandlerと同じなら意味がない。

メリットは以下のように独自に命名できることぐらいか。
public delegate void fugaEventHandler(object sender, EventArgs e);
「だからどうした」「混乱するからやめてくれ」という声も聞こえなくもない。

こんな感じで引数なしとかもできちゃいますが…
public delegate void fugaEventHandler();


あとはイベントを定義して
public event fugaEventHandler fuga;

イベントハンドラの定義と同じ型のメソッドをイベントに追加する
this.fuga += new fugaEventHandler(hoge_fuga);

いつ追加するか
基本はコンストラクタで。
慣れたらイベントにイベントハンドラを追加するメソッドを作って動的に追加する、なんてことも。

が、追加されているイベントハンドラを把握する処理が書けないと危ういものに成りかねない。

どうやってそのイベントに追加されているイベントハンドラを知ることが出来るか
リフレクションで可能... かな?

ListBoxのItemsって結局のところ、こういうことでしょ - C#

ListBoxのItemsプロパティを調べるとListBox.ObjectCollectionがどうのこうのと説明されてもピンとこなかった。

だいたいプロパティにインデックスがある時点で何故に?となるでしょ。配列?インデクサ?何じゃコレ?みたいな。

リファレンスってどうしてこう回りくどくて分かりづらく書いてるのかねぇ。たまに「説明になってないぞよ」とツッコミを入れる。

だが私も少々進歩して、結局のところこういうことでしょ?みたいなモノを書いてみた。合ってるかどうかは責任持ちませぬ。

class Program
{
    static void Main(string[] args)
    {
        ParentObject po = new ParentObject();
        po.Items.Add("foo");
        po.Items.Add(123);
        Console.WriteLine(
            "Items[0] = {0}\nItems[1] = {1}",
            po.Items[0], po.Items[1]);
        Console.ReadLine();
    }
}

class ItemCollection
{
    List<object> _items;

    public ItemCollection() 
    {
        _items = new List<object>();
    }

    public object this[int index]
    {
        get
        {
            return _items[index];
        }
    }

    public void Add(object item)
    {
        _items.Add(item);
    }
}

class ParentObject
{
    ItemCollection _itemCollection;

    public ParentObject()
    {
        _itemCollection = new ItemCollection();
    }

    public ItemCollection Items
    {
        get
        {
            return _itemCollection;
        }
    }
}
ParentObjectをListBox、ItemCollectionをObjectCollectionに置き換えたら分かるかな?

ListBox.SelectedIndicesプロパティやListBox.SelectedItemsプロパティも内部で値を保持するListがいつ更新されるかは別として、こんな感じでできてるんではなかろ~か。

Sortいろいろ IComparableでCompareToを実装するとか - C#

例えばint配列ならソートするのは簡単だ。ただ、実際にはそんな簡単モノばかりを扱うわけではない。
あるクラスを作成して、そのクラスの持つ特定のプロパティによってソートしたい場合はどうすれば良いか。
それにはIComparableインターフェイスを継承し、IComparableのメンバであるCompareToメソッドを追加し、比較処理をカスタマイズする。

例えば、以下のようなクラスがあるとする。

class Item : IComparable
{
    string _name;
    int _price;

    public Item()
    {
        _name = "";
        _price = 0;
    }
    public Item(string name, int price)
    {
        _name = name;
        _price = price;
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    public int Price
    {
        get { return _price; }
        set { _price = value; }
    }

    public int CompareTo(object obj)
    {
        return this.Price - ((Item)obj).Price;
    }
}

SortメソッドはこのCompareToメソッドの比較処理によってソートされる。
public static void Main(string[] args)
{
    Item[] items = {
        new Item("foo1",1478),
        new Item("foo2",5334),
        new Item("foo3",826),
    };
    
    //ソート前のItemを表示する
    foreach (Item item in items)
    {
        Console.WriteLine("Name = {0} : Price ={1}", item.Name, item.Price);
    }

    //ソートする
    Array.Sort(items);

    Console.WriteLine("ソート後");
    //ソート後のItemを表示する
    foreach (Item item in items)
    {
        Console.WriteLine("Name = {0} : Price ={1}", item.Name, item.Price);
    }
    Console.ReadLine();
}


異なる値を比較対象にしてソートする

何で比較するか決まっていればこれで良いのだが、より柔軟に、比較処理を変更させるには?

こんなクラスがあるとして
class Cell : IComparable
{
    int _x;
    int _y;
    public Cell(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
    public int X
    {
        get { return _x; }
        set { _x = value; }
    }
    public int Y
    {
        get { return _y; }
        set { _y = value; }
    }

    public int CompareTo(object obj)
    {
        Cell cell = obj as Cell;
        return this.X - cell.X;
    }
}
このクラスをSortメソッドでソートするとXプロパティの値によって比較されソートされる。

Yプロパティの値でソートするにはどうするか。

Arrayクラスには比較処理に使用するメソッドを渡してやることが出来る。
この渡されたメソッドによって比較されソートされる。
static void Main(string[] args)
{
    Cell[] cells = {
            new Cell(91,5),
            new Cell(26,58),
            new Cell(33,34)
    };

    Array.Sort(cells, CellCompareToY);

    foreach (Cell cell in cells)
    {
        Console.WriteLine("X = " + cell.X + " , Y = " + cell.Y);
    }
}

static int CellCompareToX(Cell x, Cell y)
{
    return x.X - y.X;
}

static int CellCompareToY(Cell x, Cell y)
{
    return x.Y - y.Y;
}
「CellCompareToX」と「CellCompareToY」を用意してどちらかを渡してやることによって切り替え可能になる。

Sortを行っている箇所
Array.Sort(cells, CellCompareToY);
この部分を以下のようにしても分かりやすいかも
Array.Sort(cells, (Cell x, Cell y) => x.Y - y.Y);

Listクラスで同様のSortを行う

Listクラスでも同様に比較するメソッドを引数に渡してやることができる。
static void Main(string[] args)
{
    List<Cell> cells = new List<Cell>();

    cells.Add(new Cell(56, 93));
    cells.Add(new Cell(9, 17));
    cells.Add(new Cell(57, 11));

    cells.Sort(CompareY); //メソッドは省略
    cells.Sort((Cell x, Cell y) => x.Y - y.Y); //lambda

    foreach (Cell cell in cells)
    {
        Console.WriteLine(cell.X + " - " + cell.Y); 
    }
    Console.ReadLine();
}

Sony Style(ソニースタイル)
デル株式会社

Recent Posts