初めに
だんだんとプログラミングに慣れてくると並列処理について知ることになると思います。C#で言うならばParallel.For
だったりTask
だったりです。この二つを学ぼうとするとき障壁になるのAction<>
やFunc<>
といった、なんかよくわからないクラスたちです。
一言で言ってしまうとこれらのクラスはDelegate型と非常に密接に関わっています。
この記事でそのもやもやを少しでも解消できたら幸いです。
なおサボりたい見やすさ的に最低限のサンプルコードだけを載せます。
Delegate型って何?
簡単に言ってしまうとメソッドの参照を格納することができる型です。例を見てみましょう。一緒に手を動かしてみるとさらに理解は深まりますよ!
//namespaceなどは省略しています。
static void Main(string[] args)
{
Program p = new Program();
SampleDelegate d1 = new SampleDelegate(p.G);//p.G()じゃない
d1 += p.H;
d1.Invoke();
}
public delegate void SampleDelegate();//delegate型は一種のクラスのようなもの
public void G()
{
Console.WriteLine("G");
}
public void H()
{
Console.WriteLine("H");
}
このコードを実行するとコンソールに
G
H
と表示されるはずです。コードを見たらわかりますが先に P.G
と代入しています。ここを先にP.H
を代入すれば.Invoke()
したとき、先にP.H
が実行されます。他にも-=
を利用するとすでに代入されているメソッドの参照を消せたりするので、まあそこらへんは自分で調べてください。(丸投げ)
そもそもどこでDelegate型が使われるのか
あるボタンのクリックイベントハンドラに関数を渡すコードです。
//xamlなどで<Button Name="button">みたいにすれば
button.Click += Tekitou();
ここで.Click
に注目してください。これは厳密に言わなくてもevent
です。しかしevent
はdelegate
を基にして作ったものなので同じような動作をします。これはあくまで一例なのでほかにも探してみてください。(丸投げPart2)
いちいちDelegate型を宣言するのがだるい
見ての通り、戻り値によって宣言する必要があります。
//void関数用
public delegate void SampleDelegate();
//int関数用
public delegate int SampleDelegate2();
//string関数用
public delegate string SampleDelegate();
...
となります。ここに関数へ渡すための引数のことも考えるとさらに定義が必要となり、
となります。
Q.では頭のいい人たちはどうしたのか。
A.ジェネリック型で使いまわせるAction<>とFunc<>を定義しよう!
Actionについて
これは主に戻り値のない関数の参照を格納するためにあります。
static void Main(string[] args)
{
Action<string,string,string> action = new Action<string, string, string>(S);
//action += S2;
action.Invoke("a", "b", "c");
}
public static void S(string a,string b, string c)
{
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
public static void S2(string a, string b)
{
Console.WriteLine(a);
Console.WriteLine(b);
}
a
b
c
Actionの<>の中はactionが参照に持つ関数へ渡す引数です。コードではstring型を第三引数まで取っています。そして関数`S(string a,string b, string c)`に注目してください。これもstring型を第三引数まで取っています。Action<>と引数が一致しない場合、その関数の参照をactionに渡すことはできません。
試しにactionへS2の参照を渡してみてください。必ずエラーが起きます。これは後述するFunc<>
でも同様です。
Funcについて
Func<>
もAction<>
とほぼ同じ動作をしますが、
Func<string,string,string> func //~~~省略
このように書かれたとき、Func<>
の最後の引数はfunc
が参照に持つ関数の戻り値となります。つまりFunc<>
は戻り値がある関数の参照を格納するためにあります。コードで見てみましょう。
static void Main(string[] args)
{
Func<string,string,string,string> func = new Func<string, string, string, string>(S);
func += S2;
var results = new List<string>();//戻り値を取得するためのList
foreach (var item in func.GetInvocationList())
{
if(item is Func<string, string, string, string> f)
{
var result = f.Invoke("a","b","c");
Console.WriteLine(result.ToString());
}
}
}
public static string S(string a,string b,string c)
{
return a + b + c;
}
public static string S2(string a, string b,string c)
{
return c + b + a;
}
abc
cba
見ての通り戻り値があるためAction
より複雑になります。またforeach
内の処理は書き方がいくらかあるので試してみてください。
最後に
こう考えるとAction
やFunc
はとても便利なものですね。これらはTask
などで遊ぶ際でも出てくるのでみっちり抑えておいて損はないでしょう。この記事で少しでもモヤモヤがなくっていると幸いです。お疲れさまでした。