概要
こんだけシリーズ書いていると毎回まえがきを書くのキツイよ! もー適当にいくぞ適当に。画像もつけないぞ、もー。前回(『プログラミング C#』 その8 例外)のつづきだ。
流し読みノート
delegate ってなんぞ
- delegate はメソッドへの参照のこと。
- あーそーゆーことね完全に理解した。(←わかってない。)
- いや、アレだろ? Python が普通にやってるやつだろ? 関数を引数に渡したりするやつだろ?
# わーって言うだけの関数。
def foo():
print('わー')
# 引数に渡された関数を実行するだけの関数。
def bar(func):
func()
# bar の中で foo が呼ばれて わー って出る。
bar(foo)
我々の Python だと delegate どころか名前もついてないくらい当然に行われることだけど?(だからディスるのヤメなさい)
- delegate はメソッドへの参照を Invoke によって表現。だけど Invoke は書かなくてもよい。それを隠蔽した構文がある。
Array.FindIndex(bins, IsGreaterThanZero)
メソッドを渡すってのはこういうこと。 bins は配列で IsGreater... がメソッド。うん、 Python で馴染んでるやつと同じ。- IsGreaterThanZero を受け取る側の型は
Predicate<T> match
になっている。なんやねんそれ。Predicate
はこういう型→public delegate bool Predicate<T> (T obj);
だ。- あー、てことは C# では毎回受け取る関数のつくりによって型を指定しなきゃいけないんだ。
- ちなみに我らが Python で同じ状況で型ヒントを使おうと思ったら
typing.Callable
でサクッと指定できるよ。
- てか
IsGreaterThanZero
メソッドをPredicate<T>
って型で受け取ってんだから、メソッドはPredicate<T>
に変換できるはずだ。
// できます。
var p = new Predicate<int>(IsGreaterThanZero);
// 暗黙も可能です。
Predicate<int> p = IsGreaterThanZero;
// 他のクラスの関数ももってこれるよ
Predicate<int> p = OtherClass.IsGreaterThanZero;
// デリゲートの呼び出しはこんなふうに。
public static void CallMeRightBack(Predicate<int> userCallBack)
{
bool result = userCallBack(42);
Console.WriteLine(result);
}
うん、わかった気がする。 Pythonista 諸君にとっては↓こんな感じの理解の順番でいいんじゃないかな。
- Python みたく、メソッドを引数に渡したいが C# では問題がひとつ。そう、引数に型を指定しないといけねーのだ。だが関数の型とはなんだ?
- それは
delegate
キーワードをつかって定義できる。 - だから言い換えると、メソッドは
delegate
キーワードで定義された型に代入できるのだ。
Python と C# で比べてみる
静的型付けの C# に合わせて型ヒントをつけた場合 Python ではこうなる。
import typing
# わーって言うだけの関数。
def foo() -> None:
print('わー')
# 引数に渡された関数を実行するだけの関数。
def bar(func: typing.Callable) -> None:
func()
# bar の中で foo が呼ばれて わー って出る。
bar(foo)
同じものを C# で書くとこうなる。
// わーって言うだけの関数。
private static void Foo()
{
Console.WriteLine("わー");
}
// この Foo メソッドを受け取るための型を delegate キーワードで定義する。
// これを「特定メソッド型」という。
delegate void funcType();
// 引数に渡された関数を実行するだけの関数。
private static void Bar(funcType func)
{
func();
}
private static void Main(string[] args)
{
// Bar の中で Foo が呼ばれて わー って出る。
funcType func = Foo;
Bar(func);
}
ラムダってなんぞ
いや、ラムダは Python にあるから知ってるけれどな。 Py の lambda
は無名関数を作るキーワードだ。ほんでどうやら C# では匿名関数とか匿名メソッドってのがあるらしくてな……。
- 匿名関数は
void
以外を返すインラインメソッドのこと。 - 匿名メソッドは
delegate
で定義されたインラインメソッドのこと。 - ラムダはインラインメソッドの書き方の一種っぽい。
// 匿名メソッドはこういうの。このまま引数に渡せる。
// 返り値はコンパイラが推論してる。
delegate (int value) { return value > 0; }
// ラムダ式はこういうの。↑と同じ意味。
value => value > 0
// 書き方はたくさんある。うわぁイヤだわ。
// Predicate は delegate 型で、 bool を返すものを格納する。
Predicate<int> p = (value) => value > 0;
Predicate<int> p = (int value) => value > 0;
Predicate<int> p = value => { return value > 0; }
Predicate<int> p = (value) => { return value > 0; }
// これ↓が一般的らしい。
Predicate<int> p = (int value) => { return value > 0; }
// 引数なしはこう。
Func<bool> isAfternoon = () => { DateTime.Now.Hour >= 12; }
我らが Python ではこうね。この書き方だけ。書き方がたくさんあるのって害だと思うのだが。
lambda value: value > 0
型ヒントを導入したいならこう。
import typing
f: typing.Callable[[int], bool] = lambda value: value > 0
{}
で囲まれた部分は行数制限はない。ここは Python と明確に違うね。 Pythonista は2行になるくらいならdef
れ。議論の余地なし!- インラインメソッド内では外側の変数を参照できる。ああ、まあ Python と同じってことだよね。
# Python で書くと理解しやすい。
ZERO = 0
# 外の変数を lambda の中に持ち込める。
f = lambda value: value > ZERO
- インラインメソッドは、どーなってるかっていうと結局コンパイラがフツーのメソッドに変換して生成してる。
// インラインメソッドに大して生成されたコード。
[CompilerGenerated]
private sealed class <>c_DisplayClass1
{
public int threshold:
public bool <IsGreaterThan>b__o(int value)
{
return (value > this.threshold);
}
}
イベント
- イベントは
event
キーワードで定義されたメンバだ。
public event Action<string> Announcement;
public void Announce(string message)
{
if (Announcement != null)
{
Announcement(message);
}
}
// これ、びっくりなんだけど Announcement("わー") で呼べない。コンパイルすら出来ない。
// こんな感じで使う。
var source = new Eventful();
source.Announcement += m => Console.WriteLine("発表: " + m);
// これ本のママなんだが……
// おい、一般的なラムダ式で書けや。こうだろ→ (m) => { Console.WriteLine("発表: " + m); }
あーそーゆーことね完全に理解した。(←今度はマジでわかってない。)
だけど読んでてとくに興味がわかなかったのでこの項目は(も)スキップする。 Python の公開関数だって、時が来たらわかった。これも同じやろ。
小休憩
C# でまったく使ったことのない機能の数々なんだけど Python の知識のおかげでかろうじて本をぶん投げずに済んでいる。 C# の本なのに読めば読むほど Python に惚れ直すぜ。
ようやく9章が終わり、全体は22章。ここまでやってようやく折返しが見えたところだ。さらにサクサク進めないと飽きちゃってポイしちゃうかもしれん。サクサクいこう。ここはみろりHPだ、好きにやるぜ。