読者です 読者をやめる 読者になる 読者になる

雑記 - otherwise

最近はDQ10しかやっていないダメ技術者がちまちまと綴る雑記帳

ラムダ式の変数スコープ:注意編

C#

途中から内部の仕組みのネタに脱線しちゃいましたが、本線に戻して、ラムダ式を使う際にやっちゃいけないと思われることを幾つか挙げてみます。
……ぱっと思いついたもののみなので、これ以外にもある気はしますが、一応参考として。

スコープを抜ける前にラムダ式内で使用しているオブジェクトを破棄する

class Foo {
  Func<int> f;

  public Foo() {
    var obj = new { X = 5 };
    f = () => obj.X;
    obj = null;              // 明示的に破棄
  }

  public void Execute() {
    Console.WriteLine(f());
  }

  static void Main() {
    var foo = new Foo();
    foo.Execute();
  }
}

この例だと、 ラムダ式内で参照している Hoge オブジェクト (obj) がスコープを抜ける前に破棄されているため、 f() を実行した際に、 NullReferenceException が発生します。

using ブロックに制限された変数をラムダ式内で使用する

class Foo {
  Action act;

  public Foo() {
    using (var w = new StreamWriter("example.txt", false, Encoding.UTF8)) {
      act = () => w.Write("ヒウィッヒヒー");
    }
  }

  public void Execute() {
    act();
  }

  static void Main() {
    var foo = new Foo();
    foo.Execute();
  }
}

一つ前の話とほぼ一緒ですが、コード上で明示的に破棄するロジックを書く必要がないため見落としがちなパターンかもしれません。
上の例の場合、 act() 実行時に ObjectDisposedException が発生します。

IDisposable を実装するクラスのオブジェクトを使う

var g = pictureBox1.CreateGraphics();
act = () => g.DrawLine(Pens.Black, 0, 0, 10, 10);
// ここで g.Dispose() するとラムダ式の実行時に ObjectDisposedException が発生する。
// だからと言って Dispose() を呼ばないとリソースが解放されないのでメモリが。。。

結局、これが全てなんじゃないかって気もしますが、明示的に破棄する必要のあるオブジェクトをラムダ式内で使う場合はかなり慎重にすべきです。
特に、ラムダ式の外側で生成されたオブジェクトをラムダ式内で使う場合、ラムダ式自体がスコープを失うまで解放出来なくなるため、メモリリークの要因になりかねません。
まぁ、これを防止するには、「そんなコードを書かない」が正しいと思います。 :p
(上の例であれば、ラムダ式内で Graphics オブジェクトを取得して、線を描いた後にそのオブジェクトを破棄すれば良い)