C# でクロージャが不変ではない正当な理由はありますか?
質問
これについて頭の中で何度も考えていますが、C# クロージャーが変更可能である正当な理由が思いつきません。何が起こっているのかを正確に認識していないと、意図しない結果が生じる可能性があるため、良い方法のように思えます。
もう少し知識のある人なら、C# の設計者がクロージャ内で状態の変更を許可する理由を解明できるかもしれません。
例:
var foo = "hello";
Action bar = () => Console.WriteLine(foo);
bar();
foo = "goodbye";
bar();
これにより、最初の呼び出しのために「Hello」が印刷されますが、外部状態は2回目の呼び出しで変更され、「さようなら」を印刷します。閉鎖の状態は、ローカル変数の変更を反映するために更新されました。
解決
のC#とJavaScriptだけでなく、O'Camlとハスケル、および他の多くの言語、の字句閉鎖のとして知られているものがあります。これは内部関数は、外側の関数で、の値ののだけではなくコピーをローカル変数のの名をのアクセスできることを意味します。例えばO'CamlまたはHaskellのようなコースの不変シンボル、を有する言語では、名前の上に閉じること値より閉鎖と同一であるので、閉鎖の二つのタイプの差は消え。これらの言語は、それにもかかわらず、ちょうどC#やJavaScriptなどの字句クロージャを持っています。
他のヒント
すべてのクロージャは同じように動作しません。セマンティックするで違いがあります。
閉鎖セマンティクスのあなたの考え方が支配的な概念ではないかもしれない...最初のアイデアはマッチC#の行動を提示することに注意してください。
の理由については:私はここでの鍵はECMA、標準化グループだと思います。マイクロソフトは、ちょうどこのような場合にはその意味を次されます。
これは実際に素晴らしい機能です。これは通常、隠された何かをアクセス閉鎖、たとえば、プライベートクラス変数を持っており、それがイベントのようなものへの応答として、制御された方法でそれを操作できます。
あなたは、変数のローカルコピーを作成し、それを使用することにより、あなたは非常に簡単に欲しいものをシミュレートすることができます。
また、C#で本当に不変タイプの概念はありませんことを覚えておく必要があります。 .NET Frameworkのオブジェクト全体がちょうど(あなたがなど、明示的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();
現在の行動で意図しない結果を取得することが容易であればその疑問ですからます。
、コンパイラは、各捕獲変数のメンバーを持っているあなたのための型を作成します。あなたの例では、コンパイラは、このような何かを生成します:
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
public string foo;
public void <Main>b__0()
{
Console.WriteLine(this.foo);
}
}
それは、後に撮影した変数を使用できるように、あなたのデリゲートは、このタイプへの参照を与えています。残念ながら、foo
のローカルインスタンスはまた、彼らは同じオブジェクトを使用するよう変更がローカルにデリゲートに影響しますので、ここで指すように変更されます。
あなたは不変のも、オプションは、現在の実装でここに存在しないので、公共の場ではなく、プロパティによって処理されreadonly
の持続性を見ることができるように。私はこのようなものでなければならないであろうものをしたいと考えます:
var foo = "hello";
Action bar = [readonly foo]() => Console.WriteLine(foo);
bar();
foo = "goodbye";
bar();
不器用構文しかし考え恩赦は、出力にコンパイラへのヒントになる<=>様式で<=>捕捉されることを意味する。この生成されたタイプ:
[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);
}
}
これは、あなたが特定の方法で何を望むかあなたを与えるだろうが、コンパイラへの更新が必要になります。
に関してで なぜ 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# クロージャの違い.