9

ループ内ではなくループ外で使用される変数を宣言する方が良いですか? ループ内で変数が宣言されている例を時々見かけます。これにより、ループが実行されるたびにプログラムが新しい変数にメモリを割り当てるようになりますか? それとも、実際には同じ変数であることを認識できるほど .NET は賢いのでしょうか。

たとえば、この回答の以下のコードを参照してください。

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[32768];
    while (true)
    {
        int read = input.Read (buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write (buffer, 0, read);
    }
}

この変更されたバージョンはさらに効率的でしょうか?

public static void CopyStream(Stream input, Stream output)
{
    int read; //OUTSIDE LOOP
    byte[] buffer = new byte[32768];
    while (true)
    {
        read = input.Read (buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write (buffer, 0, read);
    }
}
4

6 に答える 6

9

いいえ、それはより効率的ではありません。ただし、とにかくループの外で宣言するために、このように書き直します。

byte[] buffer = new byte[32768];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
    output.Write(buffer, 0, read);
}

私は通常、条件で副作用を使用するのが好きではありませんが、事実上、Readメソッドはストリームの最後に到達したかどうかと、どれだけ読んだかという 2 ビットのデータを提供しています。while ループは、「データを読み取ることができたので、それをコピーします」と言っています。

を使用するのと少し似ていint.TryParseます:

if (int.TryParse(text, out value))
{
    // Use value
}

繰り返しますが、条件でメソッドを呼び出すという副作用を使用しています。私が言うように、2 ビットのデータを返すメソッドを扱っている場合、この特定のパターンを除いて、これを行う習慣はありません。

a から行を読んでも同じことが起こりますTextReader:

string line;
while ((line = reader.ReadLine()) != null)
{
    ...
}

元の質問に戻ると、変数がループの反復ごとに初期化され、ループ本体内でのみ使用される場合、ほとんどの場合、ループ内で宣言します。ここでの小さな例外の 1 つは、変数が無名関数によってキャプチャされている場合です。その時点で、動作に違いが生じます。目的の動作が得られるフォームを選択します...しかし、それはほとんどの場合、「内部で宣言する「とにかくフォーム。

編集:スコープに関しては、上記のコードは実際に変数を必要以上に大きなスコープのままにしています...しかし、ループをより明確にすることができると思います。次のことを気にする場合は、新しいスコープを導入することでいつでもこれに対処できます。

{
    int read;
    while (...)
    {
    }
}
于 2010-07-13T21:02:50.550 に答える
3

これが役に立たない可能性が低い環境では、それでもマイクロ最適化になります。明快さや適切な範囲設定などの要素は、これがほとんど違いを生まない可能性があるエッジ ケースよりもはるかに重要です。

パフォーマンスについて考えずに、変数に適切なスコープを与える必要があります。もちろん、複雑な初期化は別物です。そのため、何かを一度だけ初期化する必要があり、ループ内でのみ使用する場合でも、外部で宣言する必要があります。

于 2010-07-13T21:13:22.213 に答える
2

このような多くの単純な最適化の場合と同様に、コンパイラがそれを処理します。これらの両方を試して、ildasm でアセンブリの IL を見ると、宣言の順序が変更されていますが、両方が単一の int32 読み取り変数を宣言していることがわかります。

  .locals init ([0] int32 read,
           [1] uint8[] buffer,
           [2] bool CS$4$0000)

  .locals init ([0] uint8[] buffer,
           [1] int32 read,
           [2] bool CS$4$0000)
于 2010-07-14T01:56:45.433 に答える
2

これらの他の回答のほとんどに、警告を付けて同意します。

ラムダ式を使用している場合は、変数のキャプチャに注意する必要があります。

static void Main(string[] args)
{
    var a = Enumerable.Range(1, 3);
    var b = a.GetEnumerator();
    int x;
    while(b.MoveNext())
    {
        x = b.Current;
        Task.Factory.StartNew(() => Console.WriteLine(x));
    }
    Console.ReadLine();
}

結果が得られます

3
3
3

どこ

static void Main(string[] args)
{
    var a = Enumerable.Range(1, 3);
    var b = a.GetEnumerator();
    while(b.MoveNext())
    {
        int x = b.Current;
        Task.Factory.StartNew(() => Console.WriteLine(x));
    }
    Console.ReadLine();
}

結果が得られます

1
2
3

またはそこのいくつかの順序。これは、タスクが最終的に開始されたときに、x への参照の現在の値をチェックするためです。最初の例では、3 つのループすべてが同じ参照を指していましたが、2 番目の例では、それらはすべて異なる参照を指していました。

于 2010-07-13T21:42:04.123 に答える
1

.NETが十分に賢い場合でも、後で作業する可能性のある他の環境は十分に賢くない可能性があるため、私は一般的に後者を個人的な習慣の問題として好みました。変数を再初期化するためにループ内の余分なコード行にコンパイルするだけですが、それでもオーバーヘッドがあります。

与えられた例ですべての測定可能な目的でそれらが同一であるとしても、後者は長期的に問題を引き起こす可能性が少ないと言えます。

于 2010-07-13T21:03:52.020 に答える
1

それは本当に問題ではありません。その特定の例のコードを確認していたとしても、どちらの方法でも構いません。

ただし、クロージャーで「読み取り」変数をキャプチャすることになった場合、2 つの意味が大きく異なる可能性があることに注意してください。

foreach ループに関してこの問題が発生する、Eric Lippert によるこの優れた投稿を参照してください。有害な.aspx

于 2010-07-13T21:09:28.260 に答える