6

私は頭の中でこれを何度も繰り返してきましたが、C# クロージャが変更可能である正当な理由が思いつかないようです。何が起こっているのかを正確に認識していない場合、意図しない結果を得るには良い方法のように思えます.

たぶん、もう少し知識のある人は、C# の設計者がクロージャーで状態を変更できる理由を明らかにすることができますか?

例:

var foo = "hello";
Action bar = () => Console.WriteLine(foo);
bar();
foo = "goodbye";
bar();

これにより、最初の呼び出しでは「hello」が出力されますが、2 回目の呼び出しでは外部の状態が変化し、「さようなら」が出力されます。クロージャの状態が更新され、ローカル変数への変更が反映されました。

4

6 に答える 6

9

C# と JavaScript、O'Caml と Haskell、および他の多くの言語には、レキシカル クロージャーと呼ばれるものがあります。これは、内部関数がのコピーだけでなく、囲んでいる関数のローカル変数の名前にアクセスできることを意味します。もちろん、O'Caml や Haskell などの不変シンボルを使用する言語では、名前を閉じることは値を閉じることと同じであるため、2 つのタイプのクロージャの違いはなくなります。それにもかかわらず、これらの言語には、C# や JavaScript と同じように字句閉鎖があります。

于 2009-01-27T16:43:18.660 に答える
3

すべてのクロージャーが同じように動作するわけではありません。セマンティクスには違いがあります。

提示された最初のアイデアはC#の動作と一致することに注意してください...クロージャーセマンティクスの概念が主要な概念ではない場合があります。

理由としては、ここで重要なのは標準グループであるECMAだと思います。この場合、Microsoftはセマンティクスに従っています。

于 2009-01-27T16:20:36.803 に答える
2

これは実際には素晴らしい機能です。これにより、通常は非表示になっているもの (プライベート クラス変数など) にアクセスするクロージャーを作成し、イベントなどへの応答として、制御された方法でそれを操作できるようになります。

変数のローカルコピーを作成し、それを使用することで、必要なものを非常に簡単にシミュレートできます。

于 2009-01-27T16:30:11.917 に答える
1

また、C# には不変型の概念がないことも覚えておく必要があります。.Net フレームワーク内のオブジェクト全体がコピーされないため (ICloneable などを明示的に実装する必要があります)、このコードは、"ポインター" foo がクロージャーでコピーされた場合でも "さようなら" を出力します。

class Foo
{
    public string Text;
}    
var foo = new Foo();
foo.Text = "Hello";
Action bar = () => Console.WriteLine(foo.Text);
bar();
foo.Text = "goodbye";
bar();

したがって、現在の動作で意図しない結果が得られやすいかどうかは疑問です。

于 2009-01-27T16:51:09.923 に答える
0

C# でクロージャが変更可能である 理由については、「単純さ (Java) が必要ですか、それとも複雑さを伴う力 (C#) が必要ですか?」と尋ねる必要があります。

可変クロージャーを使用すると、一度定義して再利用できます。例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClosureTest
{
    class Program
    {   
        static void Main(string[] args)
        {
            string userFilter = "C";            
            IEnumerable<string> query = (from m in typeof(String).GetMethods()
                                         where m.Name.StartsWith(userFilter)
                                         select m.Name.ToString()).Distinct();

            while(userFilter.ToLower() != "q")
            {
                DiplayStringMethods(query, userFilter);
                userFilter = GetNewFilter();
            }
        }

        static void DiplayStringMethods(IEnumerable<string> methodNames, string userFilter)
        {
            Console.WriteLine("Here are all of the String methods starting with the letter \"{0}\":", userFilter);
            Console.WriteLine();

            foreach (string methodName in methodNames)
                Console.WriteLine("  * {0}", methodName);
        }

        static string GetNewFilter()
        {
            Console.WriteLine();
            Console.Write("Enter a new starting letter (type \"Q\" to quit): ");
            ConsoleKeyInfo cki = Console.ReadKey();
            Console.WriteLine();
            return cki.Key.ToString();
        }
    }
}

意図しない結果が懸念されるため、一度定義して再利用したくない場合は、単純に変数のコピーを使用できます。上記のコードを次のように変更します。

        string userFilter = "C";
        string userFilter_copy = userFilter;
        IEnumerable<string> query = (from m in typeof(String).GetMethods()
                                     where m.Name.StartsWith(userFilter_copy)
                                     select m.Name.ToString()).Distinct();

これで、何userFilterが等しいかに関係なく、クエリは同じ結果を返します。

Jon Skeet は、 Java と C# のクロージャの違いについて優れた紹介をしています。

于 2009-11-09T06:35:08.227 に答える
0

クロージャーを作成すると、コンパイラーは、キャプチャーされた各変数のメンバーを持つ型を作成します。あなたの例では、コンパイラは次のようなものを生成します。

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    public string foo;

    public void <Main>b__0()
    {
        Console.WriteLine(this.foo);
    }
}

デリゲートには、キャプチャされた変数を後で使用できるように、この型への参照が与えられます。残念ながら、 のローカル インスタンスfooもここを指すように変更されているため、ローカルで変更を加えるとデリゲートが同じオブジェクトを使用するため影響を受けます。

ご覧のとおり、の永続性はfooプロパティではなくパブリック フィールドによって処理されるため、現在の実装では不変性のオプションさえありません。あなたが望むものは次のようなものでなければならないと思います:

var foo = "hello";
Action bar = [readonly foo]() => Console.WriteLine(foo);
bar();
foo = "goodbye";
bar();

不器用な構文を許してください。しかし、アイデアは、この生成された型を出力するようにコンパイラに示唆する方法でfooキャプチャされていることを示すことです。readonly

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    public readonly string foo;

    public <>c__DisplayClass1(string foo)
    {
        this.foo = foo;
    }

    public void <Main>b__0()
    {
        Console.WriteLine(this.foo);
    }
}

これにより、特定の方法で必要なものが得られますが、コンパイラの更新が必要になります。

于 2009-01-27T17:06:59.143 に答える