14

C#ラムダ内でキーワードを使用できるかどうか疑問に思っていますがthis、実際には使用できることはわかっていますが、これが悪いことではないか、後で微妙な問題が発生することを確認したいと思います。

ラムダの変数スコープに関するルールを読んだら、次のことがわかります。

キャプチャされた変数は、それを参照するデリゲートがスコープ外になるまでガベージコレクションされません。

したがって、これにより、オブジェクトインスタンス(this)もキャプチャされると想定します。これをテストするために、私は実際のコードでおおよそ目指したいこの不自然な例を書きました-LINQPadで書かれているので、なぜDump()メソッド呼び出しがあるのですか?

void Main()
{
    Repository repo = new Repository();
    Person person = repo.GetPerson(1);

    person.ID.Dump("Person ID - Value Assigned");
    person.Name.Dump("Person Name - Lazily Created");
}

class Person
{
    public Person(Lazy<string> name)
    {
        this.name = name;
    }

    public int ID { get; set; }

    private Lazy<string> name;
    public string Name
    {
        get { return name.Value; }
    }
}

class Repository
{
    public Person GetPerson(int id)
    {
        // Setup person to lazily load a name value
        Person person = new Person(
            new Lazy<string>(
                () => this.GetName()    // <--- This I'm not sure on...
            )
        );
        person.ID = id;
        return person;
    }

    public string GetName()
    {
        return "John Smith";
    }
}

これが実行され、正しい出力が得られるため、ラムダ内からのアクセスthisは明らかに機能します。私がチェックしたいのは:

  • これはローカル変数と同じ変数スコープルールに従いますか?つまりthis、ラムダが使用されなくなるまで参照はメモリに保持されますか?私の小さな実験からはそう見えるでしょうが、誰かがさらに詳細を教えてくれるなら、私は興味があります。
  • これはお勧めですか?このパターンが問題を引き起こす可能性がある状況に後で入りたくありません。
4

3 に答える 3

14

thisラムダで使用することに問題はありませんが、使用するthis場合(または、非静的メンバー関数を呼び出すか、非静的メンバー変数を使用して暗黙的に使用する場合)、ガベージコレクターはthis参照するオブジェクトを保持します少なくともデリゲートが生きている限り生きること。にラムダを渡すのでLazy、これはRepository、少なくともオブジェクトが生きている限りLazy(呼び出したことがない場合でもLazy.Value)、が生きていることを意味します。

少しわかりやすくするために、逆アセンブラを調べると役立ちます。このコードを考えてみましょう:

class Foo {
    static Action fLambda, gLambda;

    int x;
    void f() {
        int y = 0;
        fLambda = () => ++y;
    }
    void g() {
        int y = 0;
        gLambda = () => y += x;
    }
}

<>標準コンパイラはこれを次のように変更します(余分な山かっこは無視してください)。ご覧のとおり、関数本体の内部から変数を使用するラムダは、次のクラスに変換されます。

internal class Foo
{
    private static Action fLambda;
    private static Action gLambda;
    private int x;

    private void f()
    {
        Foo.<>c__DisplayClass1 <>c__DisplayClass = new Foo.<>c__DisplayClass1();
        <>c__DisplayClass.y = 0;
        Foo.fLambda = new Action(<>c__DisplayClass.<f>b__0);
    }
    private void g()
    {
        Foo.<>c__DisplayClass4 <>c__DisplayClass = new Foo.<>c__DisplayClass4();
        <>c__DisplayClass.<>4__this = this;
        <>c__DisplayClass.y = 0;
        Foo.gLambda = new Action(<>c__DisplayClass.<g>b__3);
    }

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
        public int y;
        public void <f>b__0()
        {
            this.y++;
        }
    }
    [CompilerGenerated]
    private sealed class <>c__DisplayClass4
    {
        public int y;
        public Foo <>4__this;
        public void <g>b__3()
        {
            this.y += this.<>4__this.x;
        }
    }

}

を使用するthisと、暗黙的または明示的に、コンパイラによって生成されたクラスのメンバー変数になります。したがって、、のクラスにはf()DisplayClass1への参照が含まれていませんが、、のFooクラスには含まれています。g()DisplayClass2

ローカル変数を参照しない場合、コンパイラはラムダをより簡単な方法で処理します。したがって、少し異なるコードを考えてみましょう。

public class Foo {
    static Action pLambda, qLambda;

    int x;
    void p() {
        int y = 0;
        pLambda = () => Console.WriteLine("Simple lambda!");
    }
    void q() {
        int y = 0;
        qLambda = () => Console.WriteLine(x);
    }
}

今回はラムダはローカル変数を参照しないため、コンパイラはラムダ関数を通常の関数に変換します。のラムダはp()使用しないthisため、静的関数(と呼ばれる<p>b__0)になります。のラムダは(暗黙的に)q()使用するthisため、非静的関数(と呼ばれる<q>b__2)になります。

public class Foo {
    private static Action pLambda, qLambda;

    private int x;
    private void p()
    {
        Foo.pLambda = new Action(Foo.<p>b__0);
    }
    private void q()
    {
        Foo.qLambda = new Action(this.<q>b__2);
    }
    [CompilerGenerated] private static void <p>b__0()
    {
        Console.WriteLine("Simple lambda!");
    }
    [CompilerGenerated] private void <q>b__2()
    {
        Console.WriteLine(this.x);
    }
    // (I don't know why this is here)
    [CompilerGenerated] private static Action CS$<>9__CachedAnonymousMethodDelegate1;
}

: 「匿名メソッド/ラムダの逆コンパイル」オプションをオフにしてILSpyを使用てコンパイラ出力を表示しました。

于 2012-06-19T16:43:54.663 に答える
1

このようなラムダで使用するのは正しいですが、オブジェクトがガベージコレクターになるまで、オブジェクトはガベージコレクターにならないthisことに注意する必要があります。RepositoryPerson

ラムダからの結果をキャッシュするためのフィールドが必要な場合があります。レイジーがいっぱいになったら、ラムダはもう必要ないので解放します。

何かのようなもの:

private Lazy<string> nameProxy; 
private string name;
public string Name 
{ 
  get 
  {
    if(name==null)
    {
      name = nameProxy.Value;
      nameProxy = null;
    }
    return name;
  } 
} 
于 2012-06-19T15:17:17.520 に答える
0

ラムダで使用することは絶対に問題thisありませんが、覚えておくべきことがいくつかあります。

  • thisラムダが使用されなくなるまでメモリに保持されます
  • thisクラスの外でラムダ「with」を渡さない場合、問題に直面することはありません
  • クラスの外でラムダ「with」を渡す場合は、ラムダへの参照が残っているまでthis、クラスはによって収集されないことを覚えておく必要があります。GC

また、ユースケースに関連して、作成したユーザーが使用されるまで、Repositoryインスタンスが収集されることはないことに注意してくださいGC

于 2012-06-19T15:08:27.263 に答える