2

パラメータ値を変更せずに使用するために、メソッド内のローカル変数にパラメータ値を割り当てる理由はありますか? つまり、次のようにします。

private void MyMethod(string path)
{
    string myPath = path;
    StreamReader mystream = new StreamReader(myPath);
    ...
}

または、常に次のようにすることもできます (上記のコードは冗長で、きれいではありません)。

private void MyMethod(string path)
{
    StreamReader mystream = new StreamReader(path);
    ...
}

両方の方法で機能することはわかっていますが、理解に欠けているものがないことを確認したいと思います。

4

3 に答える 3

4

これを行う (ローカルに割り当てる) 必要があるのは、foreach ループを使用しているか、Linq を使用している場合のみです。そうしないと、変更されたクロージャーで問題が発生する可能性があります。

これは MSDN ブログの抜粋です (以下のすべてのコンテンツはリンクからのものです)。

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

しかし、私は自分より先に進んでいます。このフラグメントの出力は何ですか?

var values = new List<int>() { 100, 110, 120 };
var funcs = new List<Func<int>>();
foreach(var v in values) 
  funcs.Add( ()=>v );
foreach(var f in funcs) 
  Console.WriteLine(f());

ほとんどの人は、100 / 110 / 120 であると予想しています。実際には 120 / 120 / 120 です。なぜですか?

()=>v は「変数 v の現在の値を返す」ことを意味するため、「デリゲートが作成されたときに返された値 v を返す」という意味ではありません。クロージャーは、値ではなく、変数を閉じます。メソッドが実行されると、明らかに v に割り当てられた最後の値は 120 だったので、その値はまだ保持されています。

これは非常に紛らわしいです。コードを記述する正しい方法は次のとおりです。

foreach(var v in values) 
{
  var v2 = v;
  funcs.Add( ()=>v2 );
}

さてどうなる?ループ本体を再開するたびに、新しい変数 v2 を論理的に作成します。各クロージャーは、一度だけ割り当てられる異なる v2 で閉じられるため、常に正しい値が保持されます。

基本的に、foreach ループがシンタックス シュガーであることを指定しているため、問題が発生します。

 {
    IEnumerator<int> e = ((IEnumerable<int>)values).GetEnumerator();
    try
    { 
      int m; // OUTSIDE THE ACTUAL LOOP
      while(e.MoveNext())
      {
        m = (int)(int)e.Current;
        funcs.Add(()=>m);
      }
    }
    finally
    { 
      if (e != null) ((IDisposable)e).Dispose();
    }
  }

展開が

try
{ 
  while(e.MoveNext())
  {
    int m; // INSIDE
    m = (int)(int)e.Current;
    funcs.Add(()=>m);
  }

その後、コードは期待どおりに動作します。

于 2012-04-22T17:13:31.310 に答える
3

それはまったく同じことです。唯一の違いは、最初のケースでは参照のコピーを作成することです(メソッドがスコープ外になると破棄され、実行が終了すると発生します)。

読みやすくするために、2 番目のケースを使用してください。

于 2012-04-22T12:23:33.120 に答える
1

私は2番目のオプションを好みます。パラメータを使用して新しい変数を作成しても意味がありません。また、読み取りの観点からは、「myPath」変数をインスタンス化するのではなく、パス (受け取ったパス) からストリームを作成する方が理にかなっています。

于 2012-04-22T12:24:33.380 に答える