C++ を使っているチームのソースコードを見てるとクロージャという言葉をちょいちょい目にするのですが、C# だと全く見ないですね。
厳密な定義はないっぽい(ぼんやりとした定義はある)ので、人によって認識がバラバラです。
なので、「関数オブジェクトのことっぽいけど、なんでクロージャって言うんだろう?関数オブジェクトとは違うの?」という疑問があったので調べてみました。
クロージャの説明は、英語ですが↓の記事が一番分かりやすかったです。
執筆者は C++ のバイブル的な本 Effective C++ の著者です。
C++
auto f = [](int x, int y) { return x + y; };
C#
var f = (int x, int y) => x + y;
= の右側はただのラムダ式です。
左側の変数 f がクロージャのコピーです。
プログラム実行時に生成された関数(メソッド)オブジェクトのことをクロージャと呼びます。
なので、プログラムを実行しない限りクロージャは存在しません。
ソースコードに書かれているのは、ただのラムダ式で、クロージャではありません。
プログラムを実行することで、このラムダ式が関数オブジェクトとして生成されます。
関数オブジェクトとして生成されたものがクロージャで、その関数オブジェクトにアクセスするための変数が f です。
なので、「f はクロージャのコピー」と表現しました。
憶測ですが、スコープが閉じている(クローズしている)から、閉じてるモノ=クロージャという名前がついたのかな?と思います。
関数はグローバルスコープに定義を書いたり、クラスの中に書いたりしますが、ラムダ式は関数の中や、関数の中の特定のブロック内に定義を書けます。
C# だとメソッド内メソッドを定義できるので、C# で「スコープが閉じてるメソッド」と言ってもピンと来ないですね。
C#
class test {
private void hoge() {} // メソッドはクラス全体に影響する
private void moge(int a) {
if (a > 0) {
// ラムダ式はかなり狭い=閉じたスコープの中で定義できる
// なので、閉じたスコープの中に関数オブジェクトを生成できる
// 閉じたスコープの中に生成された関数オブジェクト=クロージャ
// 閉じたスコープの中でも生成できるだけで、
// グローバルスコープで生成したものも多分クロージャと言う
auto f = (int x) => Console.Write(x);
f(1234);
}
}
private void puge() {
int uge(int n) { return n * 2; } // メソッド内メソッド
Console.Write(uge(4));
}
}
C++ のファンクタは、クラスのインスタンスを関数呼び出しのように扱い、メンバ関数にアクセスするためのものです。
operator() をオーバーライドすることでファンクタを扱えるようになります。
プログラム実行時にクラスのインスタンスは生成しますが、関数オブジェクトが生成されるわけではないので、ファンクタをクロージャと呼べるのかはかなり疑問です。
クラスのインスタンス → operator() という順番でメンバ関数にアクセスしているだけなので。
C++
class test {
int a {3};
public:
int operator() (int x, int y) { return x + y + a; } //ファンクタ
};
int main() {
test t;
t(1, 2); //クラスのインスタンスをデリゲートみたいに扱える
return 0;
}
C# では operator() をオーバーライドできないので、C++ のファンクタみたいなことはできません。
デリゲートや Func<> なんかを使えば、いくらでもメンバ関数にアクセスできるので必要ないですしね…。
あと、boost::function ってファンクタって呼ぶんですかね?
あれも動的に関数オブジェクトを生成してるのでクロージャのコピーと言っても良さそうです。
boost::function がファンクタでクロージャを扱うものなら、C# の Func<> と Action<> もファンクタでクロージャを扱うものと呼べそうです。
デザインパターンなんかもそうなんですが、こういう用語の定義って掘り下げて行くとよく分からなくなります…。
