C Sharpの基礎 - デリゲート

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

  • デリゲートとは、メソッドを参照する型である。
  • LINQ等で使用されるAction<T>Func<T, TResult>は、デリゲート型の1つである。
  • デリゲート型を作成するためだけに、クラスメソッドかインスタンスメソッドを定義するのは面倒である。
  • 匿名関数を使用すれば、メソッドを定義せずにインラインに処理を記述して、デリゲート型を作成することができる。
  • 匿名関数には2種類あり、その1つがラムダ式である。(正確には、C#のラムダ式はシンタックスであり、匿名関数ではない)
  • ラムダ式はデリゲート型の作成だけでなく、式ツリー型の生成にも使用できる。
  • 匿名関数は、コンパイラがクラスメソッド・インスタンスメソッド・クラス等を内部的に作成して、それを参照するデリゲートを作っている。



デリゲート - Delegate型

C#におけるデリゲートとは、メソッドを参照する型のことである。

メソッドを参照するデリゲートを、引数として渡したり、戻り値を返している。
このデリゲートの存在が、LINQやReactive Extensionsを支えている。

全てのデリゲートは、Delegate型から派生した型である。

デリゲート型を作成するには、以下のようにdelegateキーワードを使用する。
これにより、Delegate型(正確には、MulticastDelegate型)のサブクラスが定義される。

 // int型を引数にとり、int型を返すメソッドを参照するデリゲート型
 public delegate int IntToInt(int value);
 
 // string型を引数にとり、int型を返すメソッドを参照するデリゲート型
 public delegate int StringToInt(string value);
 
 // 引数が無く、int型を返すメソッドを参照するデリゲート型
 public delegate int ReturnInt();
 
 // int型を引数にとり、戻り値が無いメソッドを参照するデリゲート型
 public delegate void ActionInt(int value);
 
 // 2つのint型を引数にとり、int型を返すメソッドを参照するデリゲート型
 public delegate int IntIntToInt(int value0, int value1);


次に、クラスメソッドを参照するデリゲートのインスタンスを作成する。

 // インスタンスを作成するデリゲート型
 // int型を引数にとり、int型を返すメソッドを参照するデリゲート型
 public delegate int IntToInt(int value);


 // 参照するクラスとメソッド
 public class Calculator
 {
    public static int AddOne (int value)
    {
       return value + 1;
    }
 }


 // デリゲートのインスタンスを作成
 // AddOneメソッドを参照するIntToInt型
 IntToInt addOne = Calculator.AddOne;
 
 // Calculatorクラス内ならば、以下の記述でもよい
 // IntToInt addOne = AddOne;


最後に、インスタンスメソッドを参照するデリゲートのインスタンスを作成する。

デリゲートがインスタンスメソッドを参照する場合、どのインスタンスのメソッドを参照しているかの情報が必要である。
デリゲートは、対象のインスタンスへの参照も内部的に保持しており、
そのインスタンスにアクセスするためのプロパティとして、Delegate型はTargetプロパティを持っている。

 // インスタンスを作成するデリゲート型
 // int型を引数にとり、int型を返すメソッドを参照するデリゲート型
 public delegate int IntToInt(int value);


 public class Multiplier
 {
    readonly int number;
 
    public Multiplier(int number)
    {
       this.number = number;
    }
 
    public int Calc(int v)
    {
       return number * v;
    }
 }


MultiplierクラスのインスタンスのCalcメソッドを参照するデリゲートのインスタンスを作成するには、以下のように記述する。

 Multiplier doubler = new Multiplier(2);    // Calcメソッドは、引数で渡した数の2倍の数を返す
 IntToInt doublerIntToInt = doubler.Calc;   // doublerのCalcを参照するデリゲートを生成
 
 Multiplier trippler = new Multiplier(3);   // Calcメソッドは引数で渡した数の3倍の数を返す
 IntToInt tripplerIntToInt = trippler.Calc; // tripplerのCalcを参照するデリゲートを生成



デリゲート - Action型とFunc型

Action<T>は、T型の引数をとり、戻り値は返さないメソッドを参照するジェネリックなデリゲート型である。
Func<T, TResult>は、T型の引数をとり、TResult型を返すメソッドを参照するジェネリックなデリゲート型である。
これらは、デリゲート型の1つである。

以下の例では、Func<T, TResult>型のデリゲートのインスタンスを生成している。

 public static int AddOne(int value)
 {
    return value + 1;
 }
 
 public static int GetLength(string value)
 {
    return value.Length;
 }
 
 public static void Main(string[] args)
 {
    // intを引数に取りintを返すAddOneメソッドを参照するデリゲートを生成
    Func<int, int> addOneDelegate = AddOne;
 
    // stringを引数に取りintを返すGetLenghtメソッドを参照するデリゲートを生成
    Func<string, int> GetLengthDelegate = GetLength;
 
    // doubleを引数に取りdoubleを返すSystem.Math.Absメソッドを参照するデリゲートを生成
    Func<double, double> doubleFloatDelegate = Math.Abs;
 }


Func<T, TResult>はジェネリックなデリゲート型なので、適切に型パラメータを設定すれば、様々なメソッドを参照できます。

Func<T, TResult>以外のジェネリクなデリゲート型には、更に多くの引数をとるAction型やFunc型のデリゲート型も存在する。
これらのジェネリックなデリゲート型は、Action<T>型を除き、C# 3.0 / .NET 3.5で追加された。

以下に、メソッドを参照することができるジェネリクなデリゲート型の例を示す。

  • Action型
    引数とらず、戻り値を返さないメソッドを参照するデリゲート型
  • Action<T>型
    T型の引数をとり、戻り値を返さないメソッドを参照するデリゲート型
  • Action<T1, T2>型
    T1型、T2型の引数をとり、戻り値を返さないメソッドを参照するデリゲート型
  • Func<TResult>型
    引数とらず、TResult型の戻り値を返すメソッドを参照するデリゲート型
  • Func<T1, T2, TResult>型
    T1型、T2型の引数をとり、TResult型の戻り値を返すメソッドを参照するデリゲート型


また、Action型やFunc型以外のデリゲート型も存在する。
LINQ等ではAction型やFunc型が多く使用されるが、それら以外のデリゲート型もクラスライブラリに存在する。

Predicate<T>型というデリゲート型もその1つである。
このデリゲート型は、ListクラスのFindAllメソッドの引数として使用されており、ListクラスのConvertAllメソッド等でも使用されている。
他のデリゲート型には、Convertor<TInput, TOutput>型、ListクラスのSortメソッドで使用されているComparison<T>型等がある。


匿名関数

上記に記載した通り、デリゲートはメソッドを参照する型であり、LINQ等で使用するFunc<T, TResult>型等はデリゲートの型の1つである。

匿名関数を説明するため、以下の例では、List<T>型とFunc<T, TResult>を引数とするCountListメソッドを記述している。
第1引数であるList<T>型の変数の要素のうち、第2引数Func<T, bool>型のデリゲートを適用して、真になる要素の数を数えるメソッドである。

7行目のpredicate(element)では、T型のelementを引数に、Func<T, bool>型のデリゲートであるpredicateが参照しているメソッドを呼び出している。
(predicate(element)は、predicate.Invoke(element)とも記述できる)

 public static int CountList<T>(List<T> list, Func<T, bool> predicate)
 {
    int count = 0;
    foreach (T element in list)
    {
       // predicateが参照するメソッドを、elementを引数に渡して呼び出す
       if(predicate(element))
       { 
          count++;  // 真ならカウンタをインクリメント
       }
    }
 
    return count;
 }


以下の例では、上記のCountListメソッドを使用して、List<string>型namesの要素のうち、文字列の長さが5未満のものを数えている。

匿名関数を使用しない場合、デリゲートを作成するためだけに使用するメソッドの定義は、
デリゲートのインスタンスを生成する場所と離れた位置に記述する必要があるため、可読性が悪い。

 public static bool IsLengthLessThan5(string str)
 {
    return str.Length < 5;
 }
 
 public static void Main(String[] args)
 {
    var names = new List<string>
    {
       "Taro",
       "Jiro",
       "Saburo"
    };
 
    // IsLengthLessThan5を参照するFunc<string, bool>型のデリゲートを生成
    Func<string, bool> predicate = IsLengthLessThan5;
 
    int count = CountList(names, predicate);
 }


匿名関数とは、デリゲートのインスタンスを生成すると同時に、インラインでメソッドを定義するためのものである。
以下の例では、上記のIsLengthLessThan5メソッドを匿名関数に変更している。

 public static void Main (String[] args)
 {
    List<string> names = new List<string>
    {
        "Taro",
        "Jiro",
        "Saburo"
    };
 
    // 匿名関数を参照するFunc<string, bool>型のデリゲートを作成
    // 匿名関数は、デリゲートの生成とメソッドの定義が近い位置にある
    Func<string, bool> predicate = delegate (string str)
    {
       return str.Length < 5;
    };
 
    int count = CountList(names, predicate);
 }


上記の例は、匿名関数の一例である。
匿名関数を使用することにより、インラインで処理を記述してデリゲートを生成することができ、可読性が上がる。

しかし、C# 3.0以降では匿名関数を使用せずに、ラムダ式を使用すべきである。
ラムダ式については、こちらのページを参照すること。