4

F のスコープ内で宣言されていない関数 F 内の変数 V を参照したい場合、javascript でスコープ チェーンがどのように機能するかにより、(パフォーマンスの点で) 宣言することが有益であることを読みました。 V を参照する F のローカル変数 V2 を呼び出し、V2 を介して V によって参照されるオブジェクトにアクセスします。

この概念が C# と VB のクロージャーに適用されるかどうか疑問に思っています (ラムダを介して関数内のローカル変数にアクセスする)

Public Shared Function Example()
    Dim a = 1
    Dim b = New Object
    Return Sub()
               'when we use the variables a and b from here does it have to "go up the scope chain"
           End Sub
End Function

ところで、答えが時期尚早ではない場合、私は好むでしょう最適化はすべての悪の根源です

4

3 に答える 3

7

短い答え:いいえ。.NET は、変数を見つけるためにスコープ チェーンをたどる必要はありません。

長い答え:

次の例から始めます。

static Func<string> CaptureArgs(int a, int b)
{
    return () => String.Format("a = {0}, b = {1}", a, b);
}

static void Main(string[] args)
{
    Func<string> f = CaptureArgs(5, 10);
    Console.WriteLine("f(): {0}", f());
    // prints f(): a = 5, b = 10
}

CaptureArgsメソッド内で、スタック上ab存在します。直感的に、無名関数で変数を参照すると、関数が返され、スタック フレームがポップされてメモリから削除abれます。(これは、上向きの funargs 問題と呼ばれます)。

C# は、上向きの funargs の問題に悩まされることはありません。これは、舞台裏では、無名関数は、コンパイラによって生成されたクラスに対する手の込んだ構文糖衣にすぎないためです。上記の C# コードは次のようになります。

private sealed class <>c__DisplayClass1
{
    // Fields
    public int a;
    public int b;

    // Methods
    public string <CaptureArgs>b__0()
    {
        return string.Format("a = {0}, b = {1}", this.a, this.b);
    }
}

コンパイラは の新しいインスタンスを作成して返し、 メソッドに渡されたおよびフィールドを<>c__DisplayClass1初期化し(これにより、およびがスタックからヒープ上に存在するフィールドに効果的にコピーされます)、それを呼び出し元に返します。Callingは実際には への呼び出しです。ababCaptureArgsabf()<>c__DisplayClass1.<CaptureArgs>b__0()

aおよび でb参照されるはバニラ フィールドであるため<CaptureArgs>b__0、デリゲートによって直接参照でき、特別な種類のスコープ チェーン ルールは必要ありません。

于 2011-05-07T21:56:23.483 に答える
6

私が正しく理解している場合、JavaScript の問題は次のとおりです。(深く) ネストされたスコープ内の変数にアクセスする場合、ランタイムは変数を見つけるためにすべての親スコープをウォークスルーする必要があります。

C# または Visual Basic のラムダには、この問題はありません。

VB または C# では、コンパイラは、ラムダ関数で参照している変数を正確に認識しているため、変数への直接参照を作成できます。唯一の違いは、キャプチャされた変数(ネストされたスコープからアクセスされる変数) をローカル変数からフィールドに変換する必要があることです (一部のオブジェクトでは、クロージャとも呼ばれます)。

例を追加するには、次のような(クレイジーな)ものを書いたとします。

Func<Func<int>> Foo() {
  int x = 10;
  return () => {
    x++;
    return () => x;
  }
}

これは少しばかげていますが、ネストされたスコープを示しています。変数は 1 つのスコープで宣言され、ネストされたスコープで設定され、さらに深いスコープで読み取られます。コンパイラは次のようなものを生成します。

class Closure { 
  public Closure(int x) { this.x = x; }
  public int x;  
  public Func<int> Nested1() { 
    x++;
    return Func<int>(Nested2);
  }
  public int Nested2() { return x; }
}

Func<Func<int>> Foo() {
  var clo = new Closure(10);
  return Func<Func<int>>(clo.Nested1);
}

ご覧のとおり、スコープのチェーンをたどることはありません。変数にアクセスするたびxに、ランタイムはメモリ内の場所 (スタックではなくヒープに割り当てられている) に直接アクセスします。

于 2011-05-07T21:36:53.060 に答える
3

簡単な答え: いいえ。

C# のクロージャは静的な[[scope chain]]方法で実装され (クローズド オーバー変数は明示的に認識され、バインドされます) 、Javascript のように を通過しません。

いくつかのテストを実行して心を落ち着かせてから、心配しないでください ;-)

ハッピーコーディング。

于 2011-05-07T21:36:05.290 に答える