デリゲート
デリゲートとは
デリゲートは、日本語で「委譲する」という意味です。C# のデリゲートは、メソッドへの参照を保持するオブジェクトのことで、主にイベント処理などに用いられる重要な概念です。デリゲートを使うと、変数のようにメソッドを渡したり、複数のメソッドをまとめて呼び出したりすることができます。
サンプルプログラム
以下のサンプルでは、Operation という名前のデリゲートを定義し、加算・減算の2つのメソッドを参照しています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx701
{
delegate void Operation(int a, int b);
class Calc
{
public void Sub(int a, int b)
{
Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
}
}
class Program
{
static void Add(int a, int b)
{
Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
}
static void Main(string[] args)
{
Calc c = new Calc();
Operation o1 = new Operation(Add); // 静的メソッドを参照
Operation o2 = new Operation(c.Sub); // インスタンスメソッドを参照
o1(2, 1);
o2(2, 1);
}
}
}
2 - 1 = 1
デリゲート変数 o1、o2 に各メソッドへの参照を代入し、変数に引数を渡すだけでメソッドを呼び出すことができます。デリゲートは静的メソッド・インスタンスメソッドのどちらも参照できます。引数と戻り値の型が同じであれば、メソッド名が異なっていてもデリゲートで参照することが可能です。
図7-1. デリゲートの仕組み
複数のメソッドへのデリゲート
デリゲートの連鎖
デリゲートは += 演算子を使うことで、複数のメソッドへの参照を1つのデリゲート変数にまとめることができます。呼び出すと、追加した順にすべてのメソッドが実行されます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx702
{
class Program
{
delegate void MyAction(int a);
static void Func1(int a)
{
Console.WriteLine("a={0}", a);
}
static void Func2(int a)
{
Console.WriteLine("a*2={0}", a * 2);
}
static void Func3(int a)
{
Console.WriteLine("a*3={0}", a * 3);
}
static void Main(string[] args)
{
MyAction a = new MyAction(Func1);
a += new MyAction(Func2);
a += new MyAction(Func3);
a(3);
}
}
}
a*2=6
a*3=9
a(3) を1回呼び出すだけで、Func1・Func2・Func3 の3つのメソッドがまとめて実行されます。-= 演算子を使えば特定のメソッドをデリゲートから取り除くことも可能です。実行順序は追加された順番になります。
図7-2. 複数のメソッドへのデリゲート(+=による連鎖)
例外処理
例外とは
プログラムを実行中に発生する予期しないエラーのことを例外(Exception)と言います。例えば、配列の範囲外にアクセスしたり、ゼロで割り算をしたりした場合に例外が発生します。
C# では、例外が発生してもプログラムを正常に続けられるよう、try〜catch〜finally 構文で例外を処理することができます。
{
(通常の処理)
}
catch ((例外クラス名) 変数)
{
(例外が発生した場合の処理)
}
finally
{
(例外の有無にかかわらず必ず実行される処理)
}
サンプルプログラム — 複数の例外を捕捉する
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx703
{
class Program
{
static void Main(string[] args)
{
try
{
for (int i = 0; i <= 5; i++)
{
int a = GetNum(i);
int b = 5;
Console.Write(a + " / " + b + " = ");
Console.WriteLine(Calc(a, b));
}
}
catch (DivideByZeroException e)
{
Console.WriteLine();
Console.WriteLine("0による割り算が発生しました");
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine("配列の範囲外にアクセスしました");
}
finally
{
Console.WriteLine("終了");
}
}
private static int Calc(int a, int b)
{
return a / b;
}
public static int GetNum(int index)
{
int[] num = { 1, 2, 3, 4 };
return num[index];
}
}
}
2 / 5 = 0
3 / 5 = 0
4 / 5 = 0
配列の範囲外にアクセスしました
終了
GetNum() で要素数4の配列に対してインデックス4や5のアクセスが起きると IndexOutOfRangeException が発生します。この例外が catch で捕捉され、finally ブロックは例外の有無にかかわらず必ず実行されます。なお、例外の種類はたくさんあるため、try 一つに対して catch を複数用意することが可能です。
図7-3. try/catch による例外処理の流れ
| 例外クラス | 発生する状況 |
|---|---|
Exception | すべての例外の基底クラス |
DivideByZeroException | 整数をゼロで割ろうとした |
IndexOutOfRangeException | 配列の範囲外のインデックスにアクセスした |
NullReferenceException | null オブジェクトのメンバにアクセスした |
OverflowException | 算術演算がオーバーフローした |
FormatException | 文字列の形式が変換に適していない |
例外を発生させる
throw 文
これまでは実行時に自動的に発生する例外を捕捉する方法を学びました。C# では throw 文を使ってプログラム内から意図的に例外を発生させることもできます。例えば、引数の値が不正な場合にエラーを通知するといった使い方ができます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleEx704
{
class Program
{
static int GetNum(int i)
{
int[] nums = { 300, 600, 900 };
if (i >= nums.Length)
{
throw new IndexOutOfRangeException();
}
return nums[i];
}
static void Main(string[] args)
{
try
{
int result = GetNum(4);
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine("配列の範囲外にアクセスしました。");
}
}
}
}
GetNum() の中でインデックスが配列の範囲を超えていると判断した場合に、throw 文で IndexOutOfRangeException を発生させています。この例外は GetNum() 内では捕捉されず、呼び出し元の Main() メソッド内の try/catch によって処理されます。
図7-4. メソッドの外で例外をキャッチする仕組み