foreachループの内部または外部で変数を宣言する:どちらが高速/優れていますか?


92

これらのどれがより速く/より良いものですか?

これです:

List<User> list = new List<User>();
User u;

foreach (string s in l)
{
    u = new User();
    u.Name = s;
    list.Add(u);
}

またはこれ:

List<User> list = new List<User>();

foreach (string s in l)
{
    User u = new User();
    u.Name = s;
    list.Add(u);
}

初心者を育成するスキルから、最初のスキルの方が優れていることがわかりますが、私の友人からは間違っていると言われましたが、2番目のスキルが優れている理由はわかりませんでした。

パフォーマンスに違いはありますか?

回答:


112

パフォーマンスに関しては、両方の例が同じILにコンパイルされるため、違いはありません。

2つ目はu、ループ内でのみ使用される場合に意図をより明確に表すため、より優れています。


10
変数がラムダ式または匿名デリゲートによってキャプチャされる場合、違いあることに注意してください。外部変数トラップを参照してください。
dtb 2013

両方が同じILにコンパイルされる理由を説明できますか?C#がjavascriptのように関数の先頭まで変数宣言を巻き上げないことは、かなり確実です。
スタイル

4
@styfle があなたの質問に対する答えです。
David Sherret、2014

Stack Overflowリンクをたどると、より詳細な回答が得られます:1) Jon Hannaおよび2) StriplingWarrior
user3613932

14

いずれの場合でも、最善の方法は、名前を取得するコンストラクタを使用することです...それ以外の場合は、中括弧表記を利用します。

foreach (string s in l)
{
    list.Add(new User(s));
}

または

foreach (string s in l)
{
    list.Add(new User() { Name = s });
}

LINQ:

var list = l.Select( s => new User { Name = s});

最初の例は、場合によっては、認識できないほど高速になる可能性がありますが、2番目の例の方が読みやすいためより優れていforeachます。


6
その日の壊死性のコメント:「さらに良いのは、LINQ」。確かにそれは1行のコードであり、それが開発者としての気分を良くします。ただし、4行バージョンの方がFARの方が理解しやすく、保守が可能です。
Oskar Austegard 2013年

5
ほとんどありません。LINQバージョンを使用すると、自分がやっていることは不変であり、すべての要素で機能します。
Tordek 2013年

6

宣言によってコードが実行されることはないため、パフォーマンスの問題ではありません。

2つ目はあなたが意味することであり、2つ目の方法で行うと、愚かなエラーを起こす可能性が低くなるので、それを使用してください。変数は常に必要最小限のスコープで宣言するようにしてください。

さらに、より良い方法はLinqを使用することです:

List<User> users = l.Select(name => new User{ Name = name }).ToList();

2
「常に必要最小限のスコープで変数を宣言するようにしてください」という行が大好きです。1行で問題をうまく解決できると思います。
Manjoor

5

パフォーマンスについて質問がある場合は必ず、測定するだけです。テストの周りでループを実行し、時間を計ります。

あなたの質問に答えるために-:)を測定せずに、または生成されたイラスムを見て-有意な反復回数では違いは目立たず、コード内の最も高価な操作は、いくつかの注文によるユーザー割り当てである可能性があります規模が大きいので、コードの明快さに集中し(一般的にする必要があります)、2に進みます。

ああ、遅くなりました。このようなことを心配したり、このような詳細に巻き込まれたりしないでくださいと言っているだけだと思います。

K


チップのthx、私は私が私についても悩まされている他のいくつかのものに時間をかけるだろうと思いますhehe:D
Marcus

パフォーマンスに影響する要素をさらに詳しく調べたい場合は、コードプロファイラーの使用を検討してください。他に何もない場合は、どのタイプのコードと操作に最も時間がかかるかがわかるようになります。ProfileSharpとEqatecProfilersは無料で、使い始めるのに十分です。
Kevin Shea


1

技術的には、新しい変数を割り当てるためにスタックフレームを移動する必要がないため、最初の例では数ナノ秒を節約できますが、これは気付かないほどのCPU時間であり、コンパイラーがそうしない場合ですAnyayの違いを最適化します。


CLRがループの反復ごとに "新しい変数"を割り当てないことは確かです。
dtb 2009

まあ、コンパイラはそれをうまく最適化するかもしれませんが、ループ内の変数にはスタックスペースを割り当てる必要があります。これは実装に依存し、1つの実装はスタックフレームを同じに保つだけで、別の実装(Monoなど)はスタックを解放し、各ループでスタックを再作成できます。
Erik Funkenbusch 2009

16
メソッド内のすべてのローカル変数(トップレベルまたはループ内にネストされている)は、IL内のメソッドレベル変数にコンパイルされます。変数のスペースは、メソッドが実行される前に割り当てられます。C#で宣言された分岐に到達したときではありません。
dtb 2009

1
@dtbこの主張の出典はありますか?
スタイフル2014

1

このシナリオでは、2番目のバージョンの方が適しています。

一般に、反復の本体内の値にアクセスする必要があるだけの場合は、2番目のバージョンを選択します。一方、変数がループの本体を超えて保持する最終状態がある場合は、宣言してから最初のバージョンを使用します。




0

私はこの問題を確認しに行きました。驚いたことに、私の汚いテストで、2番目のオプションの方が常に少しでも速いことがわかりました。

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    void Method1()
    {
      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }
    }

    void Method2()
    {

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }
    }
  }

  public class User { public string Name; }
}

CILを確認しましたが、同じではありません。

ここに画像の説明を入力してください

だからもっといいテストにしたいものを用意した。

namespace Test
{
  class Loop
  { 

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    public void Method1()
    {
      sw.Restart();

      C c;
      C c1;
      C c2;
      C c3;
      C c4;

      int i = 1000;
      while (i-- > 0)
      {
        c = new C();
        c1 = new C();
        c2 = new C();
        c3 = new C();
        c4 = new C();        
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    public void Method2()
    {
      sw.Restart();

      int i = 1000;
      while (i-- > 0)
      {
        var c = new C();
        var c1 = new C();
        var c2 = new C();
        var c3 = new C();
        var c4 = new C();
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  class C { }
}

また、この場合、2番目の方法が常に勝っていましたが、CILで違いがないことを確認しました。

ここに画像の説明を入力してください

私はCILを読むグルではありませんが、デクレレーションの問題はありません。すでに指摘したように、宣言は割り当てではないので、パフォーマンスの低下はありません。

テスト

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    void Method1()
    {
      sw.Restart();

      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    void Method2()
    {
      sw.Restart();

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  public class User { public string Name; }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.