C# 基礎_デリゲート
関数指向について
---------------------------------------------------------------
関数(function)中心の設計
メソッドをオブジェクトとして扱う → デリゲート
デリゲートが有用な場面 → イベント駆動、高階関数、非同期処理
純粋な関数(pure function)
フィールド等を参照せず、同じ入力に対して常に同じ出力を返す関数
---------------------------------------------------------------
■ デリゲート
デリゲートとは、メソッドを参照するための型。
デ李ゲートは、述語やイベントハンドラ等に利用する。
インスタンスメソッドを参照したり、複数メソッドを同時に参照することができる
delegate(委譲):
「他のメソッドに処理を丸投げするためのオブジェクト」というような意味。
イベントが起きたときのイベントハンドリングをどのメソッドに丸投げ(委託)するかを
指示するためなどに使われる。
デリゲートの定義について
delegateを使用するには、まず、
delegate 戻り値の型 デリゲート型名(引数リスト);
特徴:
・定義したデリゲート型は、ユーザ定義のクラスや構造体と同じ1つの型として扱われる。
・デリゲート型は自動的にSystem,Delegateクラスの派生クラスになる。
C#のデリゲートの利点:
◇ インスタンスメソッドの代入が可能
◇ 複数のメソッドを代入できる
◇ 非同期呼び出し
◇インスタンスメソッドの代入
・クラス(static)メソッドとインスタンス(非static)メソッドの
どちらでも代入することが出来る。
◇複数のメソッドを代入する際
+=演算子を用意いることで、複数のメソッドを代入することができる。
→複数のメソッドを代入した状態で、デリゲート呼び出しを行うと、
代入した全てのメソッドが呼び出される。
複数のメソッドを格納した状態のデリゲートのことを「マルチキャストデリゲート」
と呼ぶ。
※ マルチキャストデリゲートの呼び出しは、+=で代入した順に逐次実行される。
◇ 非同期呼び出し
非同期呼び出し(Asynchronous Call)とは
メソッドを呼び出した瞬間に呼び出し元に処理が戻ってくるような呼び出しのこと。
メソッドを呼び出しをすると、デリゲートを介して呼び出されるメソッドの処理と、
呼び出し元の処理が並行して行われることになる。(マルチスレッド)
デリゲート型を定義すると、C#コンパイラによって自動的にBeginInvokeとEndInvoke
というメソッドが生成される。
BeginInbokeを用いることで、非同期呼び出しを開始し、EndInvokeを用いることにより、
非同期処理が終了を待つことができる。
BeginInvokeの処理:流れ
デリゲート型の定義時に引数リストで指定した引数と、System.AsyncCallbackデリゲート型の
引数およびobject型の引数をとり、System.IAsyncResultインターフェース型の値を返す。
EndInvokeの処理:流れ
デリゲート型の定義時にrefまたはoutキーワードを付けた引数およびSystem.IAsyncResult
インターフェースの引数を持ち、デリゲートの戻り値と同じ型の戻り値を持つ。
■ デリゲートの用途
プログラミングの世界での述語の定義
あるオブジェクトxが「xは○○である」という条件を満たすかどうか
調べるメソッドのことをいう。
例題:
static int Select(int x)
{
int n=0;
foreach(int i in x) if(i > 10) ++n;
int y = new int[n];
n=0;
foreach(int i in x)
if(i > 10)
{
y[n] = i;
++n;
}
return y;
}
例題を述語使って実装:
delegate bool Predicate(int n);
tatic int Select(int x, Predicate pred)
{
int n=0;
foreach(int i in x)
if(pred(i)) ++n;
int y = new int[n];
n=0;
foreach(int i in x)
if(pred(i))
{
y[n] = i;
++n;
}
return y;
}
例題に述語を使って、使用する際の例:
using System;
delegate bool Predicate(int n);
class DelegateTest
{
static void Main()
{
int x = new int{1, 8, 4, 11, 8, 15, 12, 19};
// x の中から値が 10 以上のもだけ取り出す
int y = Select(x, new Predicate(IsOver10));
foreach(int i in y)
Console.Write("{0} ", i);
Console.Write("\n");
// x の中から値が (5, 15) の範囲にあるものだけ取り出す
int z = Select(x, new Predicate(Is5to15));
foreach(int i in z)
Console.Write("{0} ", i);
Console.Write("\n");
}
static bool IsOver10(int n){return n > 10;}
static bool Is5to15(int n){return (n > 5) && (n < 15);}
/// <summary>
/// x の中から条件 pred を満たすものだけを取り出す。
/// </summary>
/// <param name="x">対象となる配列</param>
/// <param name="pred">述語</param>
/// <returns>条件を満たすものだけを取り出した配列</returns>
static int Select(int x, Predicate pred)
{
int n=0;
foreach(int i in x)
if(pred(i)) ++n;
int y = new int[n];
n=0;
foreach(int i in x)
if(pred(i))
{
y[n] = i;
++n;
}
return y;
}
}
■ 匿名関数
匿名関数には、C#2.0とC#3.0で導入された2つのパターンがある。
・匿名メソッド式
・ラムダ式
匿名メソッド式は概念のみ
現在使用されているのは、ラムダ式が多い。
◇匿名メソッドとは、
delegateキーワードから始まり、メソッドの中身を任意の箇所に埋め込んだ
部分のことをいう。
◇ラムダ式
・ラムダ式とは
関数(メソッド)を整数などの変数と全く同列に扱う手法のこと。
・匿名関数としても使えるもの、式木を作れる。
匿名メソッド式からラムダ式への移り変わり方
--------------------------------------------------------------
delegate(int n){ return n > 10; }
--------------------------------------------------------------
※C#2.0バージョン(匿名メソッド式)
↓
--------------------------------------------------------------
(int n) => { return n > 10; }
--------------------------------------------------------------
※C#3.0バージョン(ラムダ式)
delegateキーワードが省略されている
↓
--------------------------------------------------------------
Func<int,bool> f = n => { return n > 10; };
--------------------------------------------------------------
※C#3.0バージョン(ラムダ式)
変数の型が左辺値や関数の引数から推論できる場合には簡素化できて、
省略ができる。
(int n)の型を省略
Func(int,bool)には、デリゲートのPredicateクラスにより、
Func(T,bool)型であるため、Func(T,bool)のTが(int x)と分かっていることから
省略が可能となっている。
↓
--------------------------------------------------------------
Func<int,bool> f = n => n > 10;
--------------------------------------------------------------
※C#3.0バージョン(ラムダ式)
ラムダ式の中身がreturn文1つだけの場合
{ }やreturnも省略できる。
省略前
--------------------------------------------------------------
Func<int,bool> f = delegate(int n){ return n > 10; };
--------------------------------------------------------------
上と下のコードは同じ意味になる。
省略後
--------------------------------------------------------------
Func<int,bool> f = n => n > 10;
--------------------------------------------------------------
使える既存デリゲートメソッド
・MehtodInvokerデリゲート(System.Windows.Forms名前空間)
・Actionデリゲート(System名前空間)
・Action<T>ジェネリック・デリゲート(System名前空間)
・Action<T1,T2>ジェネリック・デリゲート(System名前空間)
・Action<T1,T2,T3>ジェネリック・デリゲート(System名前空間)
・Action<T1,T2,T3,T4>ジェネリック・デリゲート(System名前空間)
・Predicate<T>ジェネリック・デリゲート(System名前空間)
・Func<TResult>ジェネリック・デリゲート(System名前空間)
・Func<T,TResult>ジェネリック・デリゲート(System名前空間)
・Func<T1,T2,TResult>ジェネリック・デリゲート(System名前空間)
・Func<T1,T2,T3,TResult>ジェネリック・デリゲート(System名前空間)
・Func<T1,T2,T3,T4,TResult>ジェネリック・デリゲート(System名前空間)
URL:参考
https://www.atmarkit.co.jp/fdotnet/csharp30/csharp30_02/csharp30_02_02.html
■ covariance(共変性)と contravariance(反変性)
・covariance
基底クラスを戻り値とするデリゲートに対して、
派生クラスを戻り値とするメソッドを代入できること。
例:
class Base { }
class Derived : Base { }
delegate Base DelegateBaseReturn();
class Program
{
static void Main(string args)
{
Base xb;
xb = BaseReturn(); // 型が完全一致
xb = DerivedReturn(); // 基底クラスへのキャストは合法
DelegateBaseReturn db;
db = BaseReturn; // 型が完全に一致
db += DerivedReturn; // 戻り値の型が違うけど、これもok
xb = db();
}
static Base BaseReturn() { return new Base(); }
static Derived DerivedReturn() { return new Derived(); }
}
(考察)
delegate Base DelegateBaseReturn();が入ったdbに対して、
static Derived DerivedReturn() { return new Derived(); }
が派生クラス(Derived)であるため、戻り値が違っても代入可能になっている。
・contravariance
派生クラスを引数とするデリゲートに対して、基底クラスを引数とするデリゲートを
代入できること。
例:
class Base { }
class Derived : Base { }
delegate void DelegateDerivedParameter(Derived x);
class Program
{
static void Main(string[] args)
{
Derived xd = new Derived();
DerivedParameter(xd); // 型が完全一致
BaseParameter(xd); // 基底クラスへのキャストは合法
DelegateDerivedParameter dd;
dd = DerivedParameter; // 型が完全一致
dd += BaseParameter; // 引数の型が違うけど、これもok
}
static void BaseParameter(Base x) { }
static void DerivedParameter(Derived x) { }
}
(考察)
DelegateDerivedParameter(Derived x)とDerivedParameter(Derived x)は一緒だが、
BaseParameter(Base x)のように引数の型が違う場合でも、
派生クラスの引数に基底クラスは入れられる。