応用編

応用編7日目:デリゲート・例外

メソッドを参照するデリゲートと、エラーを安全に処理する例外処理を学びましょう

応用編: 1 2 3 4 5 6 7

デリゲート

デリゲートとは

デリゲートは、日本語で「委譲する」という意味です。C# のデリゲートは、メソッドへの参照を保持するオブジェクトのことで、主にイベント処理などに用いられる重要な概念です。デリゲートを使うと、変数のようにメソッドを渡したり、複数のメソッドをまとめて呼び出したりすることができます。

デリゲートの宣言書式
delegate (戻り値の型) (デリゲート名)([引数のリスト]);

サンプルプログラム

以下のサンプルでは、Operation という名前のデリゲートを定義し、加算・減算の2つのメソッドを参照しています。

プロジェクト: SampleEx701 / ファイル名: Program.cs
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 = 3
2 - 1 = 1

デリゲート変数 o1o2 に各メソッドへの参照を代入し、変数に引数を渡すだけでメソッドを呼び出すことができます。デリゲートは静的メソッド・インスタンスメソッドのどちらも参照できます。引数と戻り値の型が同じであれば、メソッド名が異なっていてもデリゲートで参照することが可能です。

デリゲートの仕組み

図7-1. デリゲートの仕組み

複数のメソッドへのデリゲート

デリゲートの連鎖

デリゲートは += 演算子を使うことで、複数のメソッドへの参照を1つのデリゲート変数にまとめることができます。呼び出すと、追加した順にすべてのメソッドが実行されます。

プロジェクト: SampleEx702 / ファイル名: Program.cs
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=3
a*2=6
a*3=9

a(3) を1回呼び出すだけで、Func1Func2Func3 の3つのメソッドがまとめて実行されます。-= 演算子を使えば特定のメソッドをデリゲートから取り除くことも可能です。実行順序は追加された順番になります。

複数のメソッドへのデリゲート

図7-2. 複数のメソッドへのデリゲート(+=による連鎖)

例外処理

例外とは

プログラムを実行中に発生する予期しないエラーのことを例外(Exception)と言います。例えば、配列の範囲外にアクセスしたり、ゼロで割り算をしたりした場合に例外が発生します。

C# では、例外が発生してもプログラムを正常に続けられるよう、try〜catch〜finally 構文で例外を処理することができます。

try〜catch〜finally の書式
try
{
(通常の処理)
}
catch ((例外クラス名) 変数)
{
(例外が発生した場合の処理)
}
finally
{
(例外の有無にかかわらず必ず実行される処理)
}

サンプルプログラム — 複数の例外を捕捉する

プロジェクト: SampleEx703 / ファイル名: Program.cs
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];
        }
    }
}
実行結果
1 / 5 = 0
2 / 5 = 0
3 / 5 = 0
4 / 5 = 0
配列の範囲外にアクセスしました
終了

GetNum() で要素数4の配列に対してインデックス4や5のアクセスが起きると IndexOutOfRangeException が発生します。この例外が catch で捕捉され、finally ブロックは例外の有無にかかわらず必ず実行されます。なお、例外の種類はたくさんあるため、try 一つに対して catch を複数用意することが可能です。

try/catchによる例外処理の流れ

図7-3. try/catch による例外処理の流れ

主な例外クラス
例外クラス発生する状況
Exceptionすべての例外の基底クラス
DivideByZeroException整数をゼロで割ろうとした
IndexOutOfRangeException配列の範囲外のインデックスにアクセスした
NullReferenceExceptionnull オブジェクトのメンバにアクセスした
OverflowException算術演算がオーバーフローした
FormatException文字列の形式が変換に適していない

例外を発生させる

throw 文

これまでは実行時に自動的に発生する例外を捕捉する方法を学びました。C# では throw 文を使ってプログラム内から意図的に例外を発生させることもできます。例えば、引数の値が不正な場合にエラーを通知するといった使い方ができます。

throw 文の書式
throw new (例外クラス名)([コンストラクタの引数, …]);
プロジェクト: SampleEx704 / ファイル名: Program.cs
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. メソッドの外で例外をキャッチする仕組み

💡 例外処理を適切に行うことで、予期しないエラーが発生してもプログラムがクラッシュせず、ユーザーに適切なメッセージを表示したり、後続の処理を安全に続けたりすることができます。

練習問題(応用編)7 へ →