■ デリゲート入門 ■ ■ 概要 昔のPC のコーディング 世界初の8 bit マイクロプロセッサは、Intel の 8008 と謂うチップで、プログラムは数字の羅列の機械 語で書いて居た。ジャンプ命令等は、飛び先のメモリの番地を直接書く訳で有る。此の為、プログラマ は、常に紙に書いたメモリマップを持って居た。 其の内に、ニーモニックが出現し、数字の羅列の機械語から、命令等は、より人間に解り易いアルファ ベットに依る文字に成った。ジャンプ先等の番地も数字でアドレスを書くのではなく、文字に依るラベ ルが使える様に成り、プログラミングの作業性が一段と向上した。文字やラベルは、アセンブラと呼ば れるプログラムが、CPU が理解出来る機械語に翻訳するので有る。 デリゲートはメソドの参照を保持 Visual Basic や C#でメソドと呼ばれる物はプログラムのブロックで、メモリー上の何処かに有る。メ ソドを呼び出すと謂う事は、機械語で謂えば、ジャンプ命令が行われると謂う事で有る。但し、 FrameWork のプログラマは、其のメソドがメモリー上の何処に有るか等と謂う事は知らなくても良い ので有る。 番地に相当する名前を書く丈でメソドを呼び出す事が、ジャンプ命令等と夢にも思っては居ない。現代 のプログラマに取っては、ジャンプ命令は禁句で、ジャンプ命令は構造化の敵なので有る。 扨て、デリゲートだが、デリゲートはメソドの参照を持つクラスで有る。参照とは平易に謂うとアドレ スで有る。詰り、デリゲートは名前でなく、メソドをアドレスで呼び出す事の出来るクラスなので有る。 クラスに有るメソドの参照(アドレス)をデリゲートに登録して置くと、其のメソドを、デリゲートで 呼び出す事が出来るので有る(厳密には、デリゲートが保持するのは、クラスのインスタンスのメソド の参照で有る)。 何故アドレスで呼び出すのか? 何故、折角名前で呼び出せる物を昔の様にアドレスで呼び出そうとするので有ろうか。例えば、或る大 きなファイルを読み書きする時、其の処理をメインスレッドで行うと、読み書きして居る間、スレッド が固まって了う(其の処理に占有される)。此れを避ける為に、通常、読み書きはメインスレッドとは 別のスレッドを作成して、非同期に操作が行われる。 斯うすると、メインスレッド上で他の作業の継続が可能に成るので有る。但し、多くの場合、呼出側の スレッドでも読み書きの終了や、読み込んだファイルの内容が知り度い場合が有る。詰り、読み書きを 指示したスレッドの指定したメソドに読み書きを仕て居るスレッドから報告(コールバック)が欲しい ので有る。
デ
デ
リ
リ
ゲ
ゲ
ー
ー
ト
ト
其れには、新しく作成された読み書きを行うスレッドに対して、此処に通知しなさいと謂う何等かの情 報を渡す必要が有る。此の為に使われるのが、通知す可きメソドのアドレスで有る。 確かに、メソドの名前を渡す方法も考えられるが、読み書きを行うスレッドを作成したスレッドも複数 のインスタンスを持って居る可能性が有り、名前を渡す場合は、何等かの方法でインスタンスを区別し なければ成らない。アドレスは、複数のインスタンスが有っても、インスタンス毎に異なる。即ち、ア ドレスを使うと、目的のインスタンスのメソドを確実に渡す事が出来る。 此の考え方は、Visual Basic や C#のイベントの方法にも採られて居る。イベントもデリゲートを使っ て行われるので有る。 デリゲートのプロトタイプ宣言 但し、デリゲートが、単純にメソドのアドレスを保持して居て、其のアドレスにジャンプした丈では 不具合が生じる。メソドには、引数を取る物や、戻り値を返す物が有るからで有る。 其処で、目的のメソドのアドレスを保持する事が出来るデリゲートクラスは、必ず其のメソドと同じ戻 り値や引数を設定出来る方法で宣言する(デリゲートのプロトタイプ宣言)。コンパイラは、其のプロト タイプを見て、其のデリゲートが目的のメソドの引数や戻り値にマッチして居るか否かを確認し、安全 性を保障する。 若し、其れが一致して居なければ、ビルド時にエラーを報告する筈で有る。詰り、デリゲートは、単に クラスのインスタンスのメソドのアドレスを保持する丈ではなく、引数と返値の受け渡しも行なうので 有る。 ■ デリゲートの実際例 下記は、文字列を引数に取り、戻り値を返さないデリゲートのクラスの宣言で有る(プロトタイプ)で 有る。此のデリゲートクラスは、同じ引数を持ち、同じ戻り値を持つメソドで有れば、何の様なメソド で有っても、其の参照(アドレス)を保持する事が出来る。 Visual Basic Delegate Sub dlgWriteString(ByVal msg As String)
C# delegate void dlgWriteString(string msg);
此のデリゲートを使って、下記のクラスのWriteMessage メソドを呼び出して観る。
Visual Basic Class WriteToScreen
Public Sub WriteMessage(ByVal msg As String) Console.WriteLine(msg) End Sub End Class C# class WriteToScreen {
public void WriteMessage(string msg) { Console.WriteLine(msg); } } デリゲートは、クラスで有る為、下記の様に、インスタンスを作り、コンストラクタには、保持す可き クラスのインスタンスのメソドを指定する。 Visual Basic
Dim Dws As New dlgWriteString(AddressOf ws.WriteMessage) C#
dlgWriteString dws = new dlgWriteString(ws.WriteMessage);
デリゲートを実行するには、下記の様にすれば良いので有る。 Visual Basic Dws(msg) C# dws(msg); Action<T> 扨て、デリゲートの宣言(プロトタイプ)だが、FrameWork には既定の宣言(既に行われて居る)が 有る。Action<T> と呼ばれる物で、1個の引数と戻り値が無いデリゲートが宣言されて居る(Action<T> は、FrameWork2.0 以上で使用可で、FrameWork3.0 以上では更に色々な既定のデリゲートが有る)。 <T>の T には、タイプが入る。上記のデリゲートは、下記の様に成る。 Visual Basic
Dim Wm As New Action(Of String)(AddressOf ws.WriteMessage) C#
Action<string> wm = new Action<string>(ws.WriteMessage);
上記で使用した下記のプロトタイプは、必要無く成る。
Visual Basic Delegate Sub dlgWriteString(ByVal msg As String)
C# delegate void dlgWriteString(string msg);
デリゲートと匿名メソド
扨て、今少し話を進めて観る事にする。C#には、匿名メソドと謂う物が有る(残念乍、Visual Basic 2005 には無い)。
public void WriteMessage(string msg) { Console.WriteLine(msg); } 上記は、WriteMessage と謂う名前を持って居る。此れに対して、匿名メソドとは、此の様な名前を持 たないメソドで有る。 此れを使うと、下記の様に、引数を持たないデリゲートを宣言する事が出来る。 C# delegate void dlgAwrite();
然して、下記の様に、インラインで、メソドを使用する事が可能と成る。
C# dlgAwrite da = delegate() {Console.WriteLine(msg);}; da(); 此の匿名メソドに依るデリゲートは、其れが実行されるクラスのスコープを持つ為に、引数の省略が出 来て、オーバーヘッドが少ない事と、デリゲートの為のメソドが同じ場所に書かれて居る為にコードが 観易い利点が有る。 デリゲートの実際例 Visual Basic Imports System Imports System.Collections.Generic Imports System.Text Module Module1
Delegate Sub dlgWriteString(ByVal Msg As String)
Sub Main()
Dim Msg As String = "Hello World!" ' 此れはデリゲートとは関係無い普通の呼出 Dim Ws As New WriteToScreen()
Ws.WriteMessage(Msg)
' デリゲートのインスタンスを作成して WriteToScreen の WriteMessage のアドレスを登録 Dim Dws As New dlgWriteString(AddressOf ws.WriteMessage)
Dws(Msg)
' FrameWork で宣言済みの Action デリゲートを使用
Dim Wm As New Action(Of String)(AddressOf ws.WriteMessage) Wm(msg)
End Sub End Module
' デリゲートで呼ばれるメソドを持つクラス Class WriteToScreen
' デリゲートで呼ばれるメソド
Public Sub WriteMessage(ByVal Msg As String) Console.WriteLine(Msg) End Sub End Class C# using System; using System.Collections.Generic; using System.Text; namespace DlegateTest {
delegate void dlgWriteString(string msg); delegate void dlgAwrite();
// デリゲートで呼ばれるメソドを持つクラス class WriteToScreen
{
// デリゲートで呼ばれるメソド
public void WriteMessage(string msg) { Console.WriteLine(msg); } } class Program {
static void Main(string[] args) {
string msg = "Hello World!";
// 此れはデリゲートとは関係無い普通の呼出 WriteToScreen ws = new WriteToScreen(); ws.WriteMessage(msg);
// デリゲートのインスタンスを作成して WriteToScreen の WriteMessage のアドレスを登録 dlgWriteString dws = new dlgWriteString(ws.WriteMessage);
// デリゲートの呼出 dws(msg);
// FrameWork で宣言済みの Action デリゲートを使用
Action<string> wm = new Action<string>(ws.WriteMessage); wm(msg);
dlgAwrite da = delegate() {Console.WriteLine(msg);}; da(); } } } デリゲート 此の記事は、ソフトウェアに関する物で、一般の人は、スルーで御願いする。 時と仕て、プログラムの解説書は、物凄く難しい説明をして有る。例えば、『プログラミングC#』と 謂う本に依ると、「デリゲートとはメソッドをカプセル化するクラスで有る」と書かれて居る。此れ を読んで直ぐに「成る程!」と思う人は、此の本を読む必要の無い人か、余程早合点の人で有ろう。 若し、「車とは、物や人を乗せる、ガソリンで走る乗り物で有る」と謂うと、電気自動車も有れば、 乗り物でない車も有ると反論されるだろう。其処で、「車とは車輪が付いた物で有る」と謂えば、多 分正しいだろうが、此れでは実際の物をイメージする事は難しい。 然う謂う事で、可成り大雑把な概念で『デリゲート』を解説して観る。 プログラムの中で関数(メソッド)は名前で呼び出される事が多い。例えば、hoge(a,b) と謂った具 合で有る。 扨て、hoge と謂う名前は、クラスの中で付いて居る。クラスは、型で有るから、スタンプの様にプロ グラムの中に、其の複製を幾つでも作る事が出来る。複製は、インスタンスと呼ばれて、実際にメモ リ上のアドレスを持つ。 通常、此のインスタンスの中の名前は、スレッドと呼ばれる物の中で管理されて居て、名前の重複は 無い。例えば、クラスのインスタンスが東京都の区だとすると、杉並区の中には hoge は一人しか許 されない。従って、杉並区の中でhoge さんを呼び出すと、正確に hoge さんに届く。併し、世田谷区 にもhoges さんが居る可能性が有る。東京都の中で hoge さんを呼ぶと、彼方の区から此方の区から もhoge さんが返事をして、杉並区の higes さんは誰だか解らない。 仕方が無いので、名前で呼ぶ事は止めて、東京都の番地で呼ぼうと謂う事に成る。東京都の番地は、 即ち、メモリ上のアドレスと謂う事に成る。何の事は無い、名前で呼ぶ代わりにアドレスで呼ぶ丈で 有る。元々、コンピューターのマシン語はアドレスで呼ぶのだから。 但し、関数の呼出は、引数が有り、戻り値が有る。其処で、戻り値や引数の形や数が同じ番地を保持 出来る箱を用意して、其の中に番地や引数を入れて引き渡そうと謂うのがデリゲートなので有る。 斯うするとスレッドを跨った関数呼出が可能と成るのだ。
■ マルチスレッド入門 ■
■ マルチスレッドとは何か(セカンドスレッドからメインスレッドを操作)
マルチスレッドとは、並列処理で有る。CLR(Common Language Runtime)は、簡単にマルチスレッ ドを実現するクラスを持って居る。 例えば、Stream クラス等はマルチスレッド使用して、ファイルの書込や読出を効率良く行う。殆どの 場合は、明示的に複数のスレッドを管理する必要は無いが、一部のクラスは、マルチスレッドの管理が 不可欠と成る。例えば、ストップウオッチをマルチスレッドで作成して、カウントはセカンドスレッド で、表示をメインスレッドで行なう場合は、スレッドを意識したコーディングが必要に成る。 亦、RS-232C の Com イベントは、セカンドスレッドで発生する、此の為、セカンドスレッドで取得し たデータをメインスレッドで表示し度い場合や、ピンチェンジをメインスレッドで表示し度い場合は、 マルチスレッドを理解する必要が有る。 此処では、最初に直面するマルチスレッドの問題点に焦点を絞って説明する(以下のマルチスレッドの 説明には、デリゲートの理解が必要で有る)。 試しに、簡単なマルチスレッドのコードを作成して観る事にする。 先ず、フォームにラベル(lblTime)を 1 個貼り付ける。続いて、ボタンを 2 個貼り付ける。1 番目の ボタン(btnCommon)を押すと、ラベルに現在の時間が表示される。2 番目のボタン(btnThread)を 押すと、第2 スレッドを作成して第 2 スレッドからラベルに時間を表示する事にする。 ボタンをクリックした時の呼び出されるジェネラルプロシージャ(関数)を、予め、下記の様に定義(記 述)して置く事にする。 Visual Basic ' 時間を表示するジェネラルプロシージャ
Private Sub SetTime() Call DisplayTime() End Sub
Private Sub DisplayTime() ' 現在の時間の表示
lblTime.Text = DateTime.UtcNow.AddHours(9).ToString("HH:mm:ss") End Sub
C# // 時間を表示するジェネラルプロシージャ
public void SetTime() { DisplayTime(); } void DisplayTime() { // 現在の時間の表示 lblTime.Text = DateTime.UtcNow.AddHours(9).ToString("HH:mm:ss"); }
1 番目のボタンのクリックイベントでは、下記の様に、単純に SetTime を呼び出す事にする。 Visual Basic Call SetTime() C# SetTime(); 2 番目のボタンのクリックイベントの中で第 2 スレッドを作成するコードは、下記の様に成る。 Visual Basic Dim T1 As System.Threading.Thread = _
New System.Threading.Thread(New ThreadStart(AddressOf SetTime)) C#
System.Threading.Thread t1 =
new System.Threading.Thread(new System.Threading.ThreadStart(SetTime));
上記のコードは、スレッドクラスのインスタンスを作成して居る。New Tread(New …) は、コンスト ラクタで有る。コンストラクタの引数とは、何で有ろうか。実は、此れはDelegate で有る。Thread コ ンストラクタはDelegate 型の引数を 1 個取る。此の引数には、CLR(Common Language Runtime) に有るThreadStart デリゲートを使用して居る。因みに、ThreadStart デリゲートは、CLR の中で、 下記の様に定義されて居る。
Visual Basic Public Delegate Sub TreadStart()
C# public delegate void TreadStart();
TreadStart デリゲートに登録されるメソドはスレッドで実行し度い SetTime メソドで有る。 扨て、スレッドのインスタンスを作成した丈では、スレッドはスタートしない。スレッドの実行をスタ ートさせる為には、下記の様に、Tread クラスの Start メソドを呼び出す必要が有る。 Visual Basic T1.Start() C# t1.Start(); 此れで第2 スレッドがスタートする。 問題は此れからで有る。第 2 スレッドの中からデリゲートで呼び出されるメソドの SetTime プロシー ジャ(メソド)の中には、現在、DisplayTime プロシージャを呼び出す処理しか記述されて居ない。此 のプロシージャは、lblTime の Text プロパティに文字列を設定する物で有る。 ※ コンピュータの設定に依り、Now では世界時が表示される事が有るので、世界時から 9 時間を加算 して、常に日本標準時を表示する様に仕て居る。 以上を実行すると、下記の例外(エラー)が発生する。
マルチスレッドの場合、メインスレッド以外のスレッドからメインスレッドに有るコントロールにアク セスしようとすると、アクセスが拒否されるので有る。其れでは、マルチスレッドの場合、メインスレ ッド以外からメインスレッドのコントロールにアクセス出来ないのだろうか。答えは『出来る』で有る。 メインスレッド以外からメインスレッドのコントロールにアクセスするには、デリゲート(委譲)を使 用する。デリゲートを使用するには、先ず、下記の様に、コードの宣言部分で、使用目的に応じた引数 を持つデリゲートを宣言する(此処では、MyDelegate と謂う名前を付けたが、任意の名前を付ける事 が出来る)。 Visual Basic ' デリゲートの宣言
Delegate Sub MyDelegate()
C# //デリゲートそ宣言する
delegate void MyDelegate();
此のデリゲートを通じて、プロシージャを呼び出す事に依り、メインスレッド以外からメインスレッド のコントロールにアクセスする事が可能に成る。
Visual Basic Invoke(New MyDelegate(AddressOf DisplayTime))
C# Invoke(new MyDelegate(DisplayTime));
メイン以外のスレッドからメインスレッドのコントロールにアクセスする場合は、Call(呼出)ではな く、Invoke(呪文で呼出)を使用する。此の Invoke は、Control.Invoke と謂われる物で、デリゲート を引数に取り、引数のデリゲートの実行をコントロールの有るメインのスレッドで行なう物で有る。
Invoke は Form が持つメソドで有り、下記の様に書き換える事が出来る(上記の記述では、恰も昔の 関数の様に観えるが、実は、フォームクラスのコードでは、フォームを表すMe や this を省略する事が 出来るので、省略して居る丈で有る)。
Visual Basic Me.Invoke(New MyDelegate(AddressOf DisplayTime))
C# this.Invoke(new MyDelegate(DisplayTime));
亦、ラベルやボタンと謂うコントロールも、Form から派生した物なので、当然、Invoke メソドを継承 して居る。従って、上記は、下記の様に記述する事も可能で、実行結果は同じに成る。
Visual Basic lblTime.Invoke(New MyDelegate(AddressOf DisplayTime)) btnCommon.Invoke(New MyDelegate(AddressOf DisplayTime))
C# lblTime.Invoke(new MyDelegate(DisplayTime)); btnCommon.Invoke(new MyDelegate(DisplayTime)); 処で、SetTime プロシージャ(メソド)は、ボタン 1 を押した時にも呼び出される。ボタン 1 を押した 時は、通常のメインスレッドからの呼出と成り、Invoke メソドを使う必要は無い(使用しても、例外 は発生せず、正しく動作する)。其処で、Invoke メソドが必要か否かを判断する InvokeRequired と謂 うメソドが有り、Invoke が必要な時は True(C#では true)を返す。下記のコードは、Invoke が必要 か否か、即ち、メソドがメインスレッドから呼び出されたのか、又は、メインスレッド以外から呼び出 されたかを判断して処理を行う為の条件分岐で有る。 Visual Basic If InvokeRequired Then C# if (InvokeRequired) 従って、SetTime プロシージャ(メソド)を、下記の様に記述すれば、メソドがメインスレッドから呼 び出されたのか、又は、メインスレッド以外から呼び出されたかを判断して処理を行う事が出来る。 Visual Basic ' 時間を表示するジェネラルプロシージャ
Private Sub SetTime() If InvokeRequired Then
' Invoke が必要な場合は Invoke で表示
Invoke(New MyDelegate(AddressOf DisplayTime)) Else DisplayTime() End If End Sub C# // 時間を表示するジェネラルプロシージャ
public void setTime() { if (InvokeRequired) // Invoke が必要な場合は Invoke で表示 Invoke(new MyDelegate(DisplayTime)); else DisplayTime(); }
以上を纏めると、下記の様に成る。
・マルチスレッドは、Threading の Tread クラスを使用する。
・Tread クラスのコンストラクタは TreadStart デリゲートを引数に取る。 ・TreadStart デリゲートにはスレッドで実行したいメソドを登録する。 ・Tread を実行するには Trear クラスの Start メソドを呼び出す。
・メインスレッド以外からメインスレッドのコントロールにアクセスする場合は Control.Invoke を使 用する。
・Invoke メソドはデリゲートを引数に取り、其のデリゲートには実行したいメソドを登録する。
サンプルコード
Visual Basic、C#共に、フォームに、ラベル 1 個(lblTime)とボタン 2 個(btnCommon、btnThread) を貼り付ける。
Visual Basic Imports System.Threading
Public Class thread ' デリゲートの宣言
Delegate Sub MyDelegate()
' 時間を表示するジェネラルプロシージャ Private Sub SetTime()
If InvokeRequired Then
' Invoke が必要な場合は Invoke で表示
Invoke(New MyDelegate(AddressOf DisplayTime)) Else
DisplayTime() End If
End Sub
Private Sub DisplayTime() ' 現在の時間の表示
lblTime.Text = DateTime.UtcNow.AddHours(9).ToString("HH:mm:ss") End Sub
' ボタン(Common)がクリックされた時の処理
Private Sub btnCommon_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCommon.Click Call SetTime()
End Sub
' ボタン(Thread)がクリックされた時の処理
Private Sub btnThread_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnThread.Click
Dim T1 As System.Threading.Thread = _
New System.Threading.Thread(New ThreadStart(AddressOf SetTime)) T1.Start()
End Sub End Class
C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; namespace thread {
public partial class thread : Form {
//デリゲートの宣言
delegate void MyDelegate(); public thread() { InitializeComponent(); } // 時間を表示するジェネラルプロシージャ public void SetTime()
{ if (InvokeRequired) // Invoke が必要な場合は Invoke で表示 Invoke(new MyDelegate(DisplayTime)); else DisplayTime(); } void DisplayTime() { // 現在の時間の表示 lblTime.Text = DateTime.UtcNow.AddHours(9).ToString("HH:mm:ss"); } // ボタン(Common)がクリックされた時の処理
private void btnCommon_Click(object sender, EventArgs e) {
SetTime(); }
// ボタン(Thread)がクリックされた時の処理
private void btnThread_Click(object sender, EventArgs e) {
System.Threading.Thread t1 =
new System.Threading.Thread(new System.Threading.ThreadStart(SetTime)); t1.Start();
} } }
■ イベント入門 ■ ■ イベントとは ボタンをクリックするとクリックイベントが発生する。亦、テキストボックスに文字を入力するとテキ ストチェンジイベントが発生する。此の様な事は説明されなくても解る思う。 其れでは、イベントは何の様に作られるのだろうか。此処では、イベントが何の様に作成されるのかコ ードのサンプルを示し乍説明する。 ■ イベントで時計を作成 何の変哲も無い時計を考えて観る事にする。 上図のプログラムでは、プログラムをスタートすると、秒単位の時間が表示される。時間の表示は、ラ ベルのテキストとフォームのテキスト両方に表示する。 扨て、実際のプログラムで有るが、時間を表示するフォームとクラス、其れと時間を計測するClock ク ラス、イベントの引数のEventArgs から派生し、イベントの時間を保持する TimeInfoEventArgs クラ スの3 個を持つ。 起動フォームでは、Clock のインスタンスを作成して、此の中のメソドの Run を呼び出す。呼出は、フ ォームのLoad が終わってからとする為、タイマーを使って、タイマー内から呼び出す物とする。
時間は、Clock クラスの Run メソドで計測される。Run は、無限のループに成って居て、秒が変わる とイベントが発生する。
Visual Basic でイベントを発生には、イベントを発生させるクラスで Event ステートメントに依りイベ ントを宣言し、RaiseEvent ステートメントに依りイベントを発生させる。
Visual Basic では、イベントの宣言の仕方に 2 種類有る。先ず、下記の様に、デリゲートを先ず宣言し、 其の後にEvent ステートメントに依りイベントを宣言する方法が有る。
Visual Basic
Public Event OnSecondChange(ByVal sender As Object, ByVal ti As TimeInfoEventArgs)
次に、下記の様に、デリゲートとイベントを同時に宣言する方法で有る。
Visual Basic
一方、C#では、デリゲートを使用して、イベントを発生させる。此の方法は、Visual Basic の初めに書 いた方法と同じで有る。
C# // デリゲートの宣言
public delegate void SecondChangeHandler(object clock, TimeInfoEventArgs timeInformation);
// イベントの作成
public event SecondChangeHandler OnSecondChange;
孰れにしろ、イベントは、デリゲートに依って実現される事を理解する事が重要で有る。
ループの中の Application.DoEvents は、ループ処理中に、他のスレッドに処理を回す為の物で有り、 System.Threading.Thread.Sleep(2) もループの実行を 2mSec 休む事に依り、CPU の負荷を下げ、亦、 他のスレッドに処理を回す。 イベントの引数と仕ては、イベント発生元の Clock のインスタンスと、EventArgs から継承された TimeInfoEventArgs が渡される。表示される時間は、此の中の TimeInfoEventArgs が保持して居る。 引数にイベント発生元のインスタンスと、EventArgs、又は、其れの継承された物を指定するのは、イ ベントの慣例で有り、必然性は無いが、コードを解りや易くする為にも、 慣例を守り度い。 扨て、イベントを受信する方で有るが、イベントの登録は、下記の様に記述する。此の場合は、複数登 録が可能な事を示す為に、2 個のメソドを登録して居る。此のイベントの登録は、幾つでも登録出来、 登録されて順番に実行される。 Visual Basic
AddHandler MyClock.OnSecondChange, AddressOf SecondChanged AddHandler MyClock.OnSecondChange, AddressOf FormTextChange
C#
myClock.OnSecondChange += new Clock.SecondChangeHandler(SecondChanged); myClock.OnSecondChange += new Clock.SecondChangeHandler(FormTextChange);
イベントは、デリゲートのリストを保持するクラス内のフィールドで有ると謂われる。此れは何の様な 意味なのだろうか。 OnSecondChange は、Clock クラスの中に有って、リスト形式で、デリゲートの参照を保持して居るの で有る。リストは、新しくイベントを登録する事も可能で有り、実行は登録順に行われる。C#の場合、 イベントの登録取り消しに、-= を使用するのも興味深い処で有る。 C#には、匿名メソドを謂う物が有り、メソドを別に記述しなくても、コード其の物を記述出来る。 C#
myClock.OnSecondChange += new Clock.SecondChangeHandler(SecondChanged);
//イベントから呼ばれるメソド
{ lblTime.Text = string.Format("{0:00}",ti.hour) + ":" + string.Format("{0:00}",ti.minute) + ":" + string.Format("{0:00}",ti.second); } //匿名メソドで記述
myClock.OnSecondChange += delegate(object theClock, TimeInfoEventArgs ti) {
lblTime.Text = string.Format("{0:00}", ti.hour) + ":" + string.Format("{0:00}", ti.minute) + ":" + string.Format("{0:00}", ti.second); }; 匿名メソドは、コードの中に埋め込む物で、最後にセミコロン( ; )が付くので注意され度い。此の匿 名メソドを使用する時は、SecondChanged のメソドは不要と成る。 最後にForm1_FormClosing の中に書かれた、Environment.Exit(0)で有るが、プログラムを終了しよ うと仕て、×を押し、プログラムが終了途中に、再びループが実行され、プログラムが正常に終了しな い事が有る。Environment.Exit(0) は、プログラムを強制的に終了させるメソドで有る。 Visual Basic Public Class EventClock
Private MyClock As Clock = New Clock() ' 上のコードは下のコードでも良い
' Private WithEvents MyClock As New Clock
' フォームが読み込まれた時の処理
Private Sub EventClock_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load
tmrStart.Enabled = True End Sub
' フォームが閉じられ様と仕た時の処理
Private Sub EventClock_FormClosing(ByVal sender As Object, _
ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing Environment.Exit(0)
End Sub
' タイマーが一定間隔で自動的に行う処理
Private Sub tmrStart_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles tmrStart.Tick
' タイマーの停止
tmrStart.Enabled = False
' イベントの登録(2 個登録)
AddHandler MyClock.OnSecondChange, AddressOf SecondChanged AddHandler MyClock.OnSecondChange, AddressOf FormTextChange
' 時計の始動 MyClock.Run() End Sub
' イベントから呼ばれるメソド1
Public Sub SecondChanged(ByVal sender As Object, ByVal e As TimeInfoEventArgs) lblTime.Text = String.Format("{0:00}", e.Hour) & ":" & _
String.Format("{0:00}", e.Minute) & ":" & _ String.Format("{0:00}", e.Second)
End Sub
' イベントから呼ばれるメソド2
Public Sub FormTextChange(ByVal sender As Object, ByVal e As TimeInfoEventArgs) Me.Text = "デジタル時計 " & String.Format("{0:00}", e.Hour) & ":" & _
String.Format("{0:00}", e.Minute) & ":" & _ String.Format("{0:00}", e.Second)
End Sub End Class
' EventArgs を継承した新しい EventArgs Public Class TimeInfoEventArgs
Inherits EventArgs
' 時、分、秒のフィールド追加 Public ReadOnly Hour As Integer Public ReadOnly Minute As Integer Public ReadOnly Second As Integer
' 新しい EventArgs のコンストラクタ
Public Sub New(ByVal Hour As Integer, ByVal Minute As Integer, ByVal Second As Integer) Me.Hour = Hour Me.Minute = Minute Me.Second = Second End Sub End Class ' 時計クラス Public Class Clock
Private Hour As Integer Private Minute As Integer Private Second As Integer
' デリゲートの宣言
Public Delegate Sub SecondChangeHandler(ByVal sender As Object, _ ByVal e As TimeInfoEventArgs)
' イベントの作成
Public Event OnSecondChange As SecondChangeHandler
' 上の2行は下記の1行でも良い
' Public Event OnSecondChange(ByVal sender As Object, ByVal e As TimeInfoEventArgs)
Public Sub Run() Do ' 制御を OS に戻す Application.DoEvents() ' 2 ミリ秒の停止 System.Threading.Thread.Sleep(2) ' 現在時間(日本標準時)の設定
Dim Dt As System.DateTime = System.DateTime.UtcNow.AddHours(9)
' 秒が更新された時の処理 If Not Dt.Second = Second Then Dim Ti As TimeInfoEventArgs = _
New TimeInfoEventArgs(Dt.Hour, Dt.Minute, Dt.Second) ' イベントが登録されて居る時の処理
Try
' イベントの発生
RaiseEvent OnSecondChange(Me, Ti) Catch ' 特に何も仕無い End Try End If ' 時間の更新 Me.Second = Dt.Second Me.Minute = Dt.Minute Me.Hour = Dt.Hour Loop End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace EventClock {
public partial class EventClock : Form {
// 時計のインスタンスを作成 Clock myClock = new Clock();
public EventClock() { InitializeComponent(); } // フォームが読み込まれた時の処理
private void EventClock_Load(object sender, EventArgs e) {
tmrStart.Enabled = true; }
// フォームが閉じられ様と仕た時の処理
private void EventClock_FormClosing(object sender, FormClosingEventArgs e) {
Environment.Exit(0); }
// タイマーが一定間隔で自動的に行う処理
private void tmrStart_Tick(object sender, EventArgs e) {
// タイマーの停止
tmrStart.Enabled = false;
// イベントの登録(2 個登録)
myClock.OnSecondChange += new Clock.SecondChangeHandler(SecondChanged); myClock.OnSecondChange += new Clock.SecondChangeHandler(FormTextChange); // イベントの登録は下記の記述でも良い // myClock.OnSecondChange += SecondChanged; // 時計の始動 myClock.Run(); } // イベントから呼ばれるメソド1
public void SecondChanged(object theClock, TimeInfoEventArgs e) {
lblTime.Text = string.Format("{0:00}", e.hour) + ":" + string.Format("{0:00}", e.minute) + ":" + string.Format("{0:00}", e.second);
}
// イベントから呼ばれるメソド2
public void FormTextChange(object theClock, TimeInfoEventArgs e) {
this.Text = "デジタル時計 " + string.Format("{0:00}", e.hour) + ":" + string.Format("{0:00}", e.minute) + ":" + string.Format("{0:00}", e.second);
} }
// EventArgs を継承した新しい EventArgs public class TimeInfoEventArgs : EventArgs {
// 時、分、秒のフィールドを追加 public readonly int hour;
public readonly int minute; public readonly int second;
// 新しい EventArgs のコンストラクタ
public TimeInfoEventArgs(int hour, int minute, int second) {
this.hour = hour; this.minute = minute; this.second = second; }
}
// 時計クラス public class Clock {
private int hour, private int minute, private int second;
// デリゲートの宣言
public delegate void SecondChangeHandler(
object clock, TimeInfoEventArgs timeInformation);
// イベントの作成
public event SecondChangeHandler OnSecondChange;
public void Run() { while(true) { // 制御を OS に戻す Application.DoEvents(); // 2 ミリ秒の停止 System.Threading.Thread.Sleep(2); // 現在時間(日本標準時)の設定 System.DateTime dt = System.DateTime.UtcNow.AddHours(9); // 秒が更新された時の処理 if (dt.Second != second) { TimeInfoEventArgs timeInformation =
new TimeInfoEventArgs(dt.Hour, dt.Minute, dt.Second); // イベントが登録されて居る時の処理(デリゲートを実行)
if (OnSecondChange != null) OnSecondChange(this, timeInformation); }
// 時間の更新
this.second = dt.Second; this.minute = dt.Minute; this.hour = dt.Hour; }
} } }
■ デリゲートとイベント ■ ■ デリゲートとイベント テキストエディタで改行を行なうと、改行の前に「↓」が入る物が有る。此れは、TextChanged イベン ト内に、改行の前に「↓」を入れる処理を書けば良い丈で有るが、今回は少し捻って、TextBox クラス を継承した新しいTextBox クラスを作成し、其れに、文末が改行で有れば、イベントで知らせるコード を追加して観る事にする。 先ず、開発環境を起動して、空のフォームを作り、プルダウンメニューのプロジェクト→クラスの追加 で、新しいクラスを追加する。然して、同じ名前空間で、TextBox クラスから継承された新しい TextBox クラスを作る。 ■ イベントの publish(発行側) TextBox を継承したクラスを作成する。 Visual Basic Public Class TextBoxEx
Inherits System.Windows.Forms.TextBox C#
public class TextBoxEx : System.Windows.Forms.TextBox
※ Visual Basic も C#も殆ど変わりは無いが、Visual Basic には Inherits キーワードが付く。
扨て、此の新しいテキストボックスの中で、本来TextBox が持つ OnTextChanged を書き換えて(オー バーライド)、新しいイベントを作成する事にする。
Visual Basic Public Class EventTextbox
' 未実装 End Class
' 拡張テキストボックス Public Class TextBoxEx
Inherits System.Windows.Forms.TextBox
' デリゲートの宣言
Public Delegate Sub DelegateNewLine(ByVal sender As Object, ByVal e As EventArgs)
' デリゲートでイベントを宣言
Public Event TextNewLine As DelegateNewLine
' TextBox の OnTextChanged の上書
Protected Overrides Sub OnTextChanged(ByVal e As EventArgs) ' 本来のイベントの呼出
MyBase.OnTextChanged(e)
' Text の最後が改行で、而も、↓が無ければイベント発生 If MyBase.Text.EndsWith(Environment.NewLine) AndAlso _ Not MyBase.Text.EndsWith("↓" + Environment.NewLine) Then RaiseEvent TextNewLine(Me, e) End If End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace EventTextbox {
public partial class EventTextbox : Form { public EventTextbox() { InitializeComponent(); } } // 拡張テキストボックス
public class TextBoxEx : TextBox {
// デリゲートの宣言
public delegate void DelegateNewLine(object sender, EventArgs e);
// デリゲートでイベントを宣言
public DelegateNewLine TextNewLine;
// TextBox の OnTextChanged の上書
protected override void OnTextChanged(EventArgs e) {
// 本来のイベントの呼出 base.OnTextChanged(e);
// Text の最後が改行で、而も、↓が無ければイベントを発生
if (base.Text.EndsWith("¥r¥n") && !base.Text.EndsWith("↓¥r¥n")) TextNewLine(this, e); } } } 本来のテキストボックスで定義されたOnTextChanged イベントを書き換えるのは Overrides(C#では override)キーワードで有る。
上記のコードに於けるRaiseEvent TextNewLine(Me, e)(C#では TextNewline(this, e))では、デリゲ ートでイベントを発生させて居る。斯うする事に依り、此の拡張テキストボックスを使用するクラスの イベントを呼び出す事が出来る。猶、此の場合、引数付けなくても良いが、慣例に従い、送り手で有る 自分のインスタンスと、標準のイベント引数のEventArgs を渡して居る。 此れ丈宣言して置いて、実際の参照は、此の拡張テキストボックスを使用するフォームの中で設定する ので有る。 因みに、上記のコードで有るが、C#の場合は、イベント専用の書き方が有り、下記の様に、デリゲート でイベントの宣言にevent キーワードを付ける事で、此の変数がイベント処理専用と仕て、宣言される ので有る。 C# public DelegateNewLine TextNewLine;
↓
public event DelegateNewLine TextNewLine;
亦、本来のイベントを上書き(オーバーライド)する場合、下記の様に、本来のイベントを呼び出して 置かないと、標準の OnTextChange イベントが発生しない。イベントを総て書き換えるのなら必要は 無いが、機能を追加する場合は、元のイベントを呼び出して置く必要が有る。 Visual Basic ' 本来のイベントの呼出 MyBase.OnTextChanged(e) C# // 本来のイベントの呼出 base.OnTextChanged(e); ■ イベントの subscribe(登録側) 扨て、次は此の新しいTextBox を使用するコードで有る。前出のコードで未実装で有ったフォームクラ スに下記のコードを記述する。
Visual Basic Private Tx As TextBoxEx
' フォームが読み込まれた時の処理
Private Sub EventTextbox_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load
' 拡張テキストボックスのインスタンス生成 Tx = New TextBoxEx()
With Tx
.Size = New Size(270, 250) .Location = New Point(10, 10) .Multiline = True
End With
Me.Controls.Add(Tx)
' イベントハンドラの追加
AddHandler Tx.TextNewLine, AddressOf Tx_NewLine End Sub
' 拡張テキストボックスで TextNewLine イベントが発生した時の処理 Private Sub Tx_NewLine(ByVal sender As Object, ByVal e As EventArgs) ' 拡張テキストボックスのテキスト取得
Dim S As String = Tx.Text
' ↓を挿入
Tx.Text = S.Substring(0, S.Length - 2) & "↓" & vbCrLf ' キャレットの末尾移動 Tx.SelectionStart = Tx.Text.Length End Sub C# private TextBoxEx tx; // フォームが読み込まれた時の処理
private void EventTextbox_Load(object sender, EventArgs e) {
// 拡張テキストボックスのインスタンス生成 tx = new TextBoxEx();
tx.Size = new Size(270, 250); tx.Location = new Point(10, 10); tx.Multiline = true;
this.Controls.Add(tx);
// イベントハンドラの追加
tx.TextNewLine += new TextBoxEx.DelegateNewLine(tx_NewLine); }
// 拡張テキストボックスで TextNewLine イベントが発生した時の処理 private void tx_NewLine(object sender, EventArgs e)
{
// 拡張テキストボックスのテキスト取得 string s = tx.Text;
// ↓を挿入
tx.Text = s.Substring(0, s.Length - 2) + "↓¥r¥n";
// キャレットの末尾移動
tx.SelectionStart = tx.Text.Length; }
新しいTextBox は、総てコードで作成して居る。Visual Studio のバージョンに依っては、TextBoxEx クラスを作成して一度ビルトすると、ツールボックスの中に TextBoxEx が表示される物も有り、其の 場合は、其のコントロールをForm の上に配置すれば良い事に成る。
因みに、C#の場合、Tx.TextNewLine += new TextBoxEx.DelegateNewLine(tx_NewLine); でイベン トハンドラを追加しない場合、此のイベントを拾う事が出来ず、『オブジェクト参照がオブジェクトイ ンスタンスに設定されて居ません。』と謂うランタイムエラーが発生する。此れを避ける為に、拡張テ キストボックスのOnTextChanged イベントの上書き(オーバーライド)に於いて、インスタンスが作 成されて居なければ、イベントを起こさない為、下記の様に記述する事が望ましい。猶、Visual Basic 2005 では、言語仕様の違いで、エラーには成らない(Visual Basic と C#では、微妙にコードの仕様が 異なる)。 C# // インスタンスが有る場合而巳イベントを発生 if (TextNewLine != null) TextNewLine(this, e); 言語仕様の違いは他にも有る。C#の場合、event キーワードを使用せずに、下記の様に記述しても正常 に動作する。C#の場合、event は、デリゲートの特殊形なので有ろう。 C# // デリゲートでイベントを宣言する
public DelegateNewLine TextNewline; ■ イベントとは
イベントは、其の記述法から仕ても、デリゲートのリストを保持するクラス内のフィールドで有ると謂 える。リストに加える為には、Visual Basic では AddHandler、C#では+=演算子を使用する。
Visual Basic AddHandler Tx.TextNewLine, AddressOf myTx_NewLine
C# tx.TextNewLine += tx_NewLine;
リスト登録されたデリゲートは、登録順に実行される事に成る。亦、削除は、Visual Basic では RemoveHandler、C#では-=演算子を使用する。
■ インターフェースの基本 ■ ■ インターフェースとは 最近のコンピュータには、必ずUSB のコネクタが付いて居る、USB のコネクタには様々な周辺機器が 接続される。メモリやプリンタは勿論の事、扇風機や膝掛け迄有る。 此の様な周辺機器は USB の仕様書に基づいて作られて居る。仕様書の中で他の機器との接続部分をイ ンターフェースと呼ぶ事が有る。USB の仕様書のインターフェス部分には何の様なコネクタを使用す るのか、コネクタの何番ピンに何ボルトの電源が接続して居る等の情報が書かれて居る。.NET Framework のインターフェースも此れに似て居る。 ■ Sort メソドを使ったインターフェースの説明 Array クラスは Sort と謂うメソドを持って居る、此れを使用したコンソールコードを、下記に示す。 Visual Basic Sub Main()
Dim Ar() As Integer = {1, 7, 4} Array.Sort(Ar)
For I As Integer = 0 To (Ar.Length - 1) Console.WriteLine(Ar(I).ToString()) Next
Console.Read() End Sub
C# static void Main(string[] args)
{
int[] ar = { 1, 7, 4 }; Array.Sort(ar);
for (int i = 0; i < ar.Length; i++) Console.WriteLine(ar[i].ToString()); Console.Read(); } 実行結果は1、4、7 と表示される。此の Sort と謂うメソドは、Array クラスに格納されて居るデータ を或る規則に従って並べ替える。 例えば、Array クラスに文字列が格納されて居る場合は、Sort メソドを実行すると、文字は大文字小文 字を無視して、Ascii コード順にソートされる。亦、Array クラスに数値が格納されて居る場合は、数 値の小さい順にソートされる。 其れでは、文字列を大文字小文字を区別してソートするには何の様にするのだろうか。亦、数値を降順 にソートするには何の様にするのだろうか。更に、自分の好きな様にソートし度いと考えるかも知れな い。
此の様な色々なソートを実現する為には、ソートの方法を、其の使用者に任せて了えば良いので有る。
ソートは、本来2 個の値を比較して、小さい方を前に、大きい方を後ろに設定する。詰まり、ソートと
は、2 個の値の比較と再配置の繰り返しで有る。其処で、此の比較の方法を、Sort の使用者に開放して 了えば良い事に成る。
■ インターフェースの定義
其れでは、String クラス、又は、Integer クラスが持つ Sort メソドの 2 個の値の比較メソドを書き換え るには、何の様な事をすれば良いのだろうか。
此の方法を提供するのがインターフェースで、インターフェースは、下記の様に記述される(実際は、 此のコードは、コモンランタイムモジュール(CLR)の中に書かれて居り、此の通りではないかも知れ ない)。
Visual Basic Public Interface IComparable
Public Function CompareTo(ByVal x As Object) As Integer End Interface C# interface IComparable { int CompareTo(object x); }
此れは、Icomparable インターフェースは、ComparareTo メソドを持ち、此の CompareTo メソドは1 個のobject 型の引数を持ち、Integer の値を返すと謂う意味で有る。然して、此の CompareTo メソド が大小比較の為のメソドと成る。 併し乍、CompareTo メソドが一体何をするかは全く記述されて居ない(実装が為されて居ない)。其れ は、使用者が自由に記述する(実装する)と謂う事なので有る。 ※ インターフェースの名前は自由だが、慣例として、先頭に大文字の I を付ける事に成って居る。 インターフェースを持つクラスの記述 扨て、此のインターフェースを持つクラスは、下記の様に記述する。 Visual Basic Public Class Compare
Implements IComparable ' 実装
End Class
C# class Compare : IComparable
{
// 実装 }
Visual Basic の場合、Implements ステートメントでインターフェース名を指定し、C#の場合、クラス 名の後ろに続けてコロン( : )とインターフェース名を記述する。猶、クラスの継承とは異なり、イン ターフェースは、ピリオド( , )で区切り、幾つでも指定する事が出来る(此れを、インターフェース を実装すると謂う)。 此処で重要なルールが有る。インターフェースを実装した場合は、必ず、其のインターフェースが規定 するメソド(此の場合は CompareTo)を実装する必要が有る。若し、実装を忘れると、コンパイラー がエラーを報告する。 ■ インターフェースのメソド実装 実際に、此のクラス(InterfaceSort)に CompareTo メソドを実装して観る事にする。 Sort メソドが呼び出す ComareTo は、引数と自分を比較して、自分が小さければ負の値を、同じなら ば0 を、自分が大きければ正の値を返す。亦、Sort メソドは ComareTo を呼び出して、負の値が返れ ば、自分を前に、同じなら何もせずに、正の値が返れば、自分を後ろにする。InterfaceSort クラスに 実装するCompareTo メソドは、其の事を念頭に入れて実装する事にする。 Visual Basic ' インターフェースを実装するクラス
Public Class Compare Implements IComparable
' 内部変数
Public Value As Integer
' コンストラクタ(最初に値をセット) Public Sub New(ByVal X As Integer) Value = X
End Sub
' インターフェース IComparable で定義されて居るメソッド(要実装) Public Function CompareTo(ByVal Obj As Object) As Integer _
Implements System.IComparable.CompareTo
Dim NewValue As Integer = DirectCast(Obj, Compare).Value Return (Value - NewValue)
End Function End Class
C# // インターフェースを実装するクラス
public class Compare : IComparable {
// 内部変数 public int value;
// コンストラクタ(最初に値をセット) public Compare(int x)
{
value = x; }
// CompareTo の実装
public int CompareTo(object obj) {
int newvalue = ((Compare)obj).value; return (value - newvalue);
} }
Compare クラスは、内部に Integer 型のフィールド Value を持つ。此の Value は、此のクラスのイン スタンスが作成される時に、コンストラクタの引数として設定される。
※ 変数名は、慣例に従い、Visual Basic では、大文字から始まり、C#では、総て小文字で命名して居 る。
ComapreTo メソドは、引数に Object 型の値を取り、此の Object 型の値と現在の設定値、詰り、Value を比較する。Dim NewValue As Integer = DirectCast(Obj, Compare).Value(C#では int newvalue = ((Compare)obj).value;)は、渡された Object 型の値から比較される Integer 型の値を抽出して居る。
亦、戻り値にValue – NewValue を設定する事で、現在の設定値が引数で与えられた比較値より大きい 場合は正の値、小さい場合は負の値、等しい場合は0 を返す事に成る。 猶、Sort に関しては今少し簡単な方法が有り、此れは後述するが、此処で重要なのは、ソートの比較方 法は、IComparable インターフェースの ComapreTo メソドの比較方法に拠ると謂う事を理解する事で 有る。 IConparable インターフェースを持つクラスを使用したソート例
其れでは、IConparable インターフェースを実装した Compare クラスを使用して、Sort のコードを書 き換えて観る事にする。
Visual Basic Public Class InterfaceSort
' ボタン(SORT)がクリックされた時の処理
Private Sub btnSort_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnSort.Click
Dim Ar(2) As Compare Ar(0) = New Compare(1) Ar(1) = New Compare(7) Ar(2) = New Compare(4) Array.Sort(Ar)
lstResult.Items.Clear()
lstResult.Items.Add(Ar(I).Value) Next
End Sub End Class
' インターフェースを実装するクラス Public Class Compare
Implements IComparable
' 内部変数
Public Value As Integer
' コンストラクタ(最初に値をセット) Public Sub New(ByVal X As Integer) Value = X
End Sub
' インターフェース IComparable で定義されて居るメソッド(要実装) Public Function CompareTo(ByVal obj As Object) As Integer _
Implements System.IComparable.CompareTo
Dim NewValue As Integer = DirectCast(obj, Compare).Value Return (Value - NewValue)
End Function End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace InterfaceSort {
public partial class InterfaceSort : Form { public InterfaceSort() { InitializeComponent(); } // ボタン(SORT)がクリックされた時の処理
private void btnSort_Click(object sender, EventArgs e) {
Compare[] ar = new Compare[3]; ar[0] = new Compare(1);
ar[1] = new Compare(7); ar[2] = new Compare(4); Array.Sort(ar);
lstResult.Items.Clear();
for (int i = 0; i < ar.Length; i++)
lstResult.Items.Add(ar[i].value.ToString()); }
}
// インターフェースを実装するクラス public class Compare : IComparable {
// 内部変数 public int value; // コンストラクタ(最初に値をセット) public Compare(int x) { value = x; } // CompareTo の実装
public int CompareTo(object obj) {
int newvalue = ((Compare)obj).value; return (value - newvalue);
} } }
結果は前出のコードと同じ1、4、7 と表示される。
其れでは、今度は、Compare クラスの Value – NewValue を NewValue – Value と書き換えて観る。実 行結果は、7,4,1 と成る。詰まり、引数と現在値の大小比較を書き換えれば、好きな様にソートが出 来ると謂う事で有る。例えば、4 を必ず先頭に来る様にソートする場合は、CompareTo を下記の様に書 き換えれば良い事に成り、実行結果は、4、7、1 と成る。
Visual Basic ' CompareTo の実装
Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo
Dim NewValue As Integer = DirectCast(obj, Compare).Value If Value = 4 Then
Return –1
ElseIf NewValue = 4 Then Return 1
Else
Return (Value - NewValue) End If
End Function
C# // CompareTo の実装
public int CompareTo(object obj) {
int newvalue = ((Compare)obj).value; if (value == 4) return -1; else if (newvalue == 4) return 1; else return (newvalue-value); }
Integer クラスも String クラスも IComparable クラスを実装して居る
扨て、此処で、今一度、最初のコードを観て欲しい。Integer 型の 1 次元配列 Ar を、Array.Sort(Ar)で ソートを行なって居る。Integer クラスは、元々IComparable インターフェースを実装して居り、当然 CompareTo メソドを実装して居る。Sort メソドは、其れを使用してソートを行なって居るので有る。
■ Array クラスの持つインターフェース
Array クラスの Sort メソドには、幾つかのオーバーライドが有り、様々な引数が設定出来るが、此処 では、Array クラスを引数に持つ物と、Array クラスと IComparer を引数に持つ物を表示して居る。
名前 説明
1 Array.Sort (Array) Array の各要素に依って実装された IComparable を使用し て、1 次元 Array 全体の要素を並べ替える。
2 Array.Sort (Array, IComparer) 1 次元 Array 内の要素を、指定した IComparer を使用して並べ替える。
1番目の方法は既に説明済みで、引数に現在値と比べるObject を渡した物で有る。比較は、IComparable インターフェースの compareTo メソドが使用されて居る。此の方法は、比べる現在値をクラスの中で 保持する必要が有り、数値や文字列の比較には面倒で有る。
インターフェースIComparer の使用
2 番目の方法は、引数にソートする Array とインターフェースの IComparer を指定する方法で有る。 IComparer インターフェースの Compare メソドは、2 個の Object の引数を取り、Integer の値を返す。
下記のコードは、Compare2 と謂うクラスを宣言し、CLR が持つ IComparer インターフェスを実装さ せて居る。Compare メソドは、比べる対象を Intger 型の引数に限定して、初めの引数が小さければ負 の値を、大きければ正の値を、等しいなら0 を返す。
Visual Basic Public Class InterfaceSort
' ボタン(SORT)がクリックされた時の処理
Private Sub btnSort_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnSort.Click
Dim Ar() As Integer = {1, 7, 4} Array.Sort(Ar, New Compare2())
lstResult.Items.Clear()
For I As Integer = 0 To (Ar.Length - 1) lstResult.Items.Add(Ar(I))
Next End Sub End Class
' インターフェースを実装するクラス Public Class Compare2
Implements IComparer
' インターフェース IComparer で定義されて居るメソッド(要実装)
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _ Implements System.Collections.IComparer.Compare
Return (Integer.Parse(x) - Integer.Parse(y)) End Function End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Collections; namespace InterfaceSort {
public partial class InterfaceSort : Form { public InterfaceSort() { InitializeComponent(); } // ボタン(SORT)がクリックされた時の処理
private void btnSort_Click(object sender, EventArgs e) {
int[] ar = { 1, 7, 4 };
Array.Sort(ar, new Compare2());
lstResult.Items.Clear();
for (int i = 0; i < ar.Length; i++)
lstResult.Items.Add(ar[i].ToString()); }
}
// インターフェースを実装するクラス public class Compare2 : IComparer {
public int Compare(object x, object y) {
return ((int)x - (int)y); }
} }
ソートの方法は、Return (Integer.Parse(x) - Integer.Parse(y))(C#では return ((int)x-(int)y);)の部分 を書き換える事に依り、自由に決める事が出来る。第1 項と第 2 項を入れ替える事で、ソートが降順に 成る事を確かめて欲しい。此の方法の方が、IComparable インターフェースの CompareTo メソドを使 用するよりコードが直感的で簡単に成る。
以上、此処では、インターフェースを、.NET Framework が持つ IComparable インターフェースと IComparer インターフェースを使用して説明したが、インターフェースは自由に設定出来る。亦、イン ターフェースは、メソド丈ではなく、プロパティやインデクサを設定する事も可能で有る。 本稿は、下記のサイトの記事を元に、手を加えた物で有る。下記のサイトは、他にも色々有用な情報が 有る。 http://www.geocities.jp/hatanero/delegate.html 1 羊の皮を着た狼 VB.NET 2 Form1、Form2 の相互参照 3 Form1、Form2 の相互参照 2 4 VB.NET C# データ型の基本 5 VB.NET C# 文字列 6 VB.NET タイマー精度 7 BackgroundWorker の魅力 1.. 8 BackgroundWorker の魅力 2.. 9 VB6 のタイマー 10 コントロールの配列をインデクサ.. 11 コントロールの配列はジェネリク.. 12 インデクサ(C#、VB.NET)
13 インデクサでBit操作 14 Unicode 入門 15 デリゲート入門 16 マルチスレッド入門 17 イベント入門 18 デリゲートとイベント 18 インターフェースの基本 RichTextBox 関係 1 RichTextBox の不思議 2 テキスト色付け高速化計画 3 VB.NET RichTextBox1 4 VB.NET RichTextBox 2 RS-232C 関係 1 RS-232C の基礎 2 RS-232C の何が変わった.. 3 SerialPort クラス 4 Unicode(ユニコード)の壁 5 マルチスレッドの壁 6 RS-232C サンプルコード 7 RS-232C の HEX モニタ 8 RS-232C 送信モジュール 9 RS-232C のループテスト 10 RS-232C のピンチェンジ.. Socket 通信 1 C#、VB2005 で Socket 通信 2 サーバー 複数接続