.NETの「クロージャー」とは何ですか?


195

クロージャーとは何ですか?.NETにありますか?

それらが.NETに存在する場合、それを説明するコードスニペット(できればC#で)を提供していただけませんか?

回答:


258

このトピックに関する記事あります。(多くの例があります。)

本質的に、クロージャーは後で実行できるコードのブロックですが、最初に作成された環境を維持します。つまり、クロージャーは、それを作成したメソッドのローカル変数などを引き続き使用できます。メソッドの実行が終了しました。

クロージャーの一般的な機能は、匿名メソッドとラムダ式によってC#に実装されています。

無名メソッドを使用した例を次に示します。

using System;

class Test
{
    static void Main()
    {
        Action action = CreateAction();
        action();
        action();
    }

    static Action CreateAction()
    {
        int counter = 0;
        return delegate
        {
            // Yes, it could be done in one statement; 
            // but it is clearer like this.
            counter++;
            Console.WriteLine("counter={0}", counter);
        };
    }
}

出力:

counter=1
counter=2

ここでは、CreateAction自体が終了した場合でも、CreateActionによって返されたアクションがまだカウンター変数にアクセスでき、実際にそれをインクリメントできることがわかります。


57
ジョン、ありがとう。ところで、.NETで知らないことはありますか?:)質問があるとき、誰に行きますか?
開発者

44
学ぶべきことは常にあります:) C#を介してCLRを読み終えたところです-非常に有益です。それ以外は、通常、WCF /バインディング/式ツリーについてはMarc Gravellに、C#言語についてはEric Lippertに尋ねます。
Jon Skeet、

2
私はそれに気づきましたが、それでも「後で実行できるコードのブロック」であるというあなたの声明は単に間違っていると思います-それは実行とは何の関係もなく、変数値と実行よりもスコープと関係があります、それ自体。
Jason Bunting

11
クロージャーは実行できないと役に立たないと思います。「後で」は、環境をキャプチャーできる「奇妙さ」を強調します(そうでなければ実行時間までに消えてしまうかもしれません)。文の半分だけを引用すれば、それはもちろん不完全な答えです。
Jon Skeet、

4
@SLC:はい、 counterインクリメントできます-コンパイラーはcounterフィールドを含むクラスを生成し、参照するコードはすべてcounterそのクラスのインスタンスを通過します。
Jon Skeet

22

C#がClosureを実装する方法を確認することに興味がある場合は、「私は答えを知っています(その42)ブログ」を読んでください

コンパイラーは、バックグラウンドでクラスを生成して、匿名メソッドと変数jをカプセル化します

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    public <>c__DisplayClass2();
    public void <fillFunc>b__0()
    {
       Console.Write("{0} ", this.j);
    }
    public int j;
}

関数の場合:

static void fillFunc(int count) {
    for (int i = 0; i < count; i++)
    {
        int j = i;
        funcArr[i] = delegate()
                     {
                         Console.Write("{0} ", j);
                     };
    } 
}

それを次のように変えます:

private static void fillFunc(int count)
{
    for (int i = 0; i < count; i++)
    {
        Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
        class1.j = i;
        Program.funcArr[i] = new Func(class1.<fillFunc>b__0);
    }
}

こんにちは、Daniil-あなたの回答は非常に役に立ちます。私はあなたの回答を超えてフォローアップしたかったのですが、リンクが壊れています。残念ながら、私のgooglefuは、移動先を見つけるには十分ではありません。
ノックス

10

クロージャーは、元のスコープの変数値を保持する機能値です。C#では、匿名のデリゲートの形式でそれらを使用できます。

非常に単純な例として、次のC#コードを見てください。

    delegate int testDel();

    static void Main(string[] args)
    {
        int foo = 4;
        testDel myClosure = delegate()
        {
            return foo;
        };
        int bar = myClosure();

    }

その最後で、barは4に設定され、myClosureデリゲートを渡して、プログラムの他の場所で使用できます。

クロージャーは、実行の遅延やインターフェースの簡素化など、多くの便利な目的に使用できます。LINQは主にクロージャーを使用して構築されています。ほとんどの開発者にとって最も便利な方法は、動的に作成されたコントロールにイベントハンドラーを追加することです。別の場所にデータを保存するのではなく、コントロールがインスタンス化されるときにクロージャーを使用して動作を追加できます。


10
Func<int, int> GetMultiplier(int a)
{
     return delegate(int b) { return a * b; } ;
}
//...
var fn2 = GetMultiplier(2);
var fn3 = GetMultiplier(3);
Console.WriteLine(fn2(2));  //outputs 4
Console.WriteLine(fn2(3));  //outputs 6
Console.WriteLine(fn3(2));  //outputs 6
Console.WriteLine(fn3(3));  //outputs 9

クロージャーは、それが作成された関数の外部に渡される無名関数です。それはそれが使用するそれが作成される関数からのすべての変数を維持します。


4

これは、JavaScriptの同様のコードから作成したC#の不自然な例です。

public delegate T Iterator<T>() where T : class;

public Iterator<T> CreateIterator<T>(IList<T> x) where T : class
{
        var i = 0; 
        return delegate { return (i < x.Count) ? x[i++] : null; };
}

だから、ここに上記のコードを使用する方法を示すいくつかのコードがあります...

var iterator = CreateIterator(new string[3] { "Foo", "Bar", "Baz"});

// So, although CreateIterator() has been called and returned, the variable 
// "i" within CreateIterator() will live on because of a closure created 
// within that method, so that every time the anonymous delegate returned 
// from it is called (by calling iterator()) it's value will increment.

string currentString;    
currentString = iterator(); // currentString is now "Foo"
currentString = iterator(); // currentString is now "Bar"
currentString = iterator(); // currentString is now "Baz"
currentString = iterator(); // currentString is now null

少しお役に立てば幸いです。


1
あなたは例を挙げましたが、一般的な定義を提供していません。私はここであなたのコメントから収集しましたが、それらは「範囲についての詳細」ですが、確かにそれ以上のものがありますか?
ladenedge 2010

2

基本的にクロージャは、関数の引数として渡すことができるコードのブロックです。C#は、匿名デリゲートの形式でクロージャーをサポートします。

簡単な例を次に示します
。List.Findメソッドは、コード(クロージャ)を受け入れて実行し、リストのアイテムを検索できます。

// Passing a block of code as a function argument
List<int> ints = new List<int> {1, 2, 3};
ints.Find(delegate(int value) { return value == 1; });

C#3.0構文を使用すると、次のように記述できます。

ints.Find(value => value == 1);

1
技術的なことは嫌いですが、クロージャはスコープとの関係がさらに強くなります。クロージャはいくつかの異なる方法で作成できますが、クロージャは手段ではありません。それが目的です。
Jason Bunting

2

クロージャーとは、関数が別の関数(またはメソッド)内で定義され、親メソッドの変数を使用する場合です。メソッド内に配置され、メソッド内で定義された関数にラップされた変数のこの使用は、クロージャーと呼ばれます。

Mark Seemannは、彼のブログ記事で、クロージャーの興味深い例をいくつか挙げています。そこでは、oopと関数型プログラミングの間で並行しています。

そしてそれをより詳細にするために

var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable
Func<int, string> read = id =>
    {
        var path = Path.Combine(workingDirectory.FullName, id + ".txt");//is used inside this function
        return File.ReadAllText(path);
    };//the entire process is called a closure.

1

クロージャーは、それ自体の外部にある変数を参照する(コードスタックの下から)コードのチャンクであり、後で呼び出されたり実行されたりする可能性があります(イベントまたはデリゲートが定義されている場合など)。 )...コードのチャンクが参照する外部変数がスコープから外れる可能性があるため(そうでなければ失われる可能性があるため)、それがコードのチャンク(クロージャーと呼ばれる)によって参照されるという事実は、ランタイムに「ホールド"その変数は、コードのクロージャチャンクで必要なくなるまでスコープ内にあります...


他の誰かの説明で述べたように、私は技術的であるのが嫌いですが、クロージャはスコープと関係があります-クロージャはいくつかの異なる方法で作成できますが、クロージャは手段ではありません。それが目的です。
Jason Bunting

1
クロージャーは私にとって比較的新しいので、誤解している可能性は十分にありますが、スコープの部分はわかります。私の答えはスコープに焦点を当てています。だから私は年のコメントが修正しようとしているものを見逃しています...他に何がスコープに関連することができますが、いくつかのコードの塊?(関数、匿名メソッドなど)
Charles Bretana、2009年

いくつかの「実行可能なコードのチャンク」が、その変数の構文的に「外側」にある変数またはメモリ内の値にアクセスできるクロージャーの鍵ではありません。その変数は通常、「スコープ外」になっているか、破棄されているはずです。 ?
Charles Bretana、2009年

そして、@ Jason、技術的なことについての心配はありません。このクロージャーのアイデアは、同僚との長い議論の中で、JavaScriptのクロージャーについて頭を包み込むのにしばらくかかったものです...彼の説明の抽象化をかなり通り抜けた...
チャールズ・ブレタナ2009年

0

私もそれを理解しようと努めてきましたが、以下は、JavaScriptとC#の同じコードのコードスニペットがクロージャを示しています。

  1. 各イベントが発生した回数、または各ボタンがクリックされた回数をカウントします。

JavaScript:

var c = function ()
{
    var d = 0;

    function inner() {
      d++;
      alert(d);
  }

  return inner;
};

var a = c();
var b = c();

<body>
<input type=button value=call onClick="a()"/>
  <input type=button value=call onClick="b()"/>
</body>

C#:

using System.IO;
using System;

class Program
{
    static void Main()
    {
      var a = new a();
      var b = new a();

       a.call();
       a.call();
       a.call();

       b.call();
       b.call();
       b.call();
    }
}

public class a {

    int b = 0;

    public  void call()
    {
      b++;
     Console.WriteLine(b);
    }
}
  1. countクリックイベントが発生した合計回数、または制御に関係なく合計クリック数をカウントします。

JavaScript:

var c = function ()
{
    var d = 0;

    function inner() {
     d++;
     alert(d);
  }

  return inner;
};

var a = c();

<input type=button value=call onClick="a()"/>
  <input type=button value=call onClick="a()"/>

C#:

using System.IO;
using System;

class Program
{
    static void Main()
    {
      var a = new a();
      var b = new a();

       a.call();
       a.call();
       a.call();

       b.call();
       b.call();
       b.call();
    }
}

public class a {

    static int b = 0;

    public void call()
    {
      b++;
     Console.WriteLine(b);
    }
}

0

ほんの少し、本C#7.0一言で言うと、シンプルで理解しやすい答えです。

前提条件:ラムダ式は、ローカル変数と、それが定義されているメソッド(外部変数)のパラメーターを参照できます。

    static void Main()
    {
    int factor = 2;
   //Here factor is the variable that takes part in lambda expression.
    Func<int, int> multiplier = n => n * factor;
    Console.WriteLine (multiplier (3)); // 6
    }

実数部:ラムダ式によって参照される外部変数は、キャプチャ変数と呼ばれます。変数をキャプチャするラムダ式は、クロージャーと呼ばれます。

最後の注意点:キャプチャされた変数は、変数がキャプチャされたときではなく、デリゲートが実際に呼び出されたときに評価されます。

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30

0

インラインの匿名メソッド(C#2)または(できれば)Lambda式(C#3 +)を記述した場合でも、実際のメソッドが作成されます。そのコードが外部スコープのローカル変数を使用している場合-何らかの方法でその変数をメソッドに渡す必要があります。

たとえば、次のLinq Where句(ラムダ式を渡す単純な拡張メソッド)を使用します。

var i = 0;
var items = new List<string>
{
    "Hello","World"
};   
var filtered = items.Where(x =>
// this is a predicate, i.e. a Func<T, bool> written as a lambda expression
// which is still a method actually being created for you in compile time 
{
    i++;
    return true;
});

そのラムダ式でiを使用する場合は、作成したメソッドにそれを渡す必要があります。

したがって、最初に発生する質問は、値または参照で渡す必要があるかどうかです。

参照渡しは、その変数への読み取り/書き込みアクセスが得られるため(おそらく)、より好ましい(そして、これはC#が行うことです。Microsoftのチームが長所と短所を比較検討し、参照で行ったと思います。JonSkeetによると)記事、Javaは値渡しで行われました)。

しかし、別の質問が発生します。どこにそれを割り当てるか?

実際に/自然にスタックに割り当てる必要がありますか?まあ、それをスタックに割り当てて参照で渡すと、それ自体のスタックフレームよりも長持ちする場合があります。この例を見てみましょう:

static void Main(string[] args)
{
    Outlive();
    var list = whereItems.ToList();
    Console.ReadLine();
}

static IEnumerable<string> whereItems;

static void Outlive()
{
    var i = 0;
    var items = new List<string>
    {
        "Hello","World"
    };            
    whereItems = items.Where(x =>
    {
        i++;
        Console.WriteLine(i);
        return true;
    });            
}

ラムダ式(Where句内)は、iを参照するメソッドを再び作成します。Outliveのスタックにiが割り当てられている場合、whereItemsを列挙するまでに、生成されたメソッドで使用されるiはOutliveのiを指します。つまり、スタック内でアクセスできなくなった場所を指します。

では、ヒープ上でそれが必要になります。

したがって、C#コンパイラがこのインラインのanonymous / lambdaをサポートするために行うことは、「Closures」と呼ばれるものを使用することです。これは、iを含むフィールドを持つDisplayClass という(かなり貧弱な)ヒープ上にクラスを作成し、実際に使用する関数それ。

これと同等のもの(ILSpyまたはILDASMを使用して生成されたILを確認できます):

class <>c_DisplayClass1
{
    public int i;

    public bool <GetFunc>b__0()
    {
        this.i++;
        Console.WriteLine(i);
        return true;
    }
}

ローカルスコープでそのクラスをインスタンス化し、iまたはラムダ式に関連するすべてのコードをそのクロージャーインスタンスで置き換えます。したがって、iが定義されている「ローカルスコープ」コードでiを使用しているときはいつでも、実際にはそのDisplayClassインスタンスフィールドを使用しています。

したがって、メインメソッドで「ローカル」のiを変更すると、実際には_DisplayClass.iが変更されます。

すなわち

var i = 0;
var items = new List<string>
{
    "Hello","World"
};  
var filtered = items.Where(x =>
{
    i++;
    return true;
});
filtered.ToList(); // will enumerate filtered, i = 2
i = 10;            // i will be overwriten with 10
filtered.ToList(); // will enumerate filtered again, i = 12
Console.WriteLine(i); // should print out 12

「i = 10」がその表示クラスフィールドに移動し、2番目の列挙の直前に変更されるため、12が出力されます。

このトピックに関する良い情報源は、このBart De Smet Pluralsightモジュールです(登録が必要です)(また、「巻上げ」という用語の誤った使用も無視してください。ローカル変数(i)が参照に変更されているということです(私が思うに)新しいDisplayClassフィールドに)。


他のニュースでは、「クロージャ」はループに関連していると誤解されているようです- 「クロージャ」はループ関連する概念ではなく、匿名メソッド/ラムダ式のローカルスコープ変数の使用-いくつかのトリック質問はループを使用してそれを示します。


-1

クロージャーは、その親変数だけでなくローカル変数にもアクセスできる、関数内で定義された関数です。

public string GetByName(string name)
{
   List<things> theThings = new List<things>();
  return  theThings.Find<things>(t => t.Name == name)[0];
}

したがって、findメソッド内の関数。

 t => t.Name == name

スコープt内の変数、およびその親スコープ内の変数名にアクセスできます。findメソッドによってデリゲートとして実行されますが、別のスコープから一緒に実行されます。


2
クロージャーはそれ自体が関数ではなく、関数よりもスコープについて話すことによって定義されます。関数は単にスコープを維持するのに役立つだけで、クロージャが作成されます。しかし、クロージャが関数であると言うことは技術的に正しくありません。nitpickでごめんなさい。:)
Jason Bunting
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.