web-dev-qa-db-ja.com

Enumerable.Rangeと従来のforループを使用したforeachの考え方

C#3.0では、このスタイルが好きです。

// Write the numbers 1 thru 7
foreach (int index in Enumerable.Range( 1, 7 ))
{
    Console.WriteLine(index);
}

従来のforループで:

// Write the numbers 1 thru 7
for (int index = 1; index <= 7; index++)
{
    Console.WriteLine( index );
}

「n」が小さいためパフォーマンスが問題にならない場合、従来のスタイルよりも新しいスタイルに反対する人はいますか?

60
Marcel Lamothe

後者の「最小から最大」形式は、この目的のためのRangeの「最小カウント」スタイルよりもはるかに明確です。また、このような変更を、より速くも、短くもなく、より馴染みがなく、明らかに明確でない規範から変更することは、本当に良いプラクティスではないと思います。

そうは言っても、私は一般的な考えに反対ではありません。 foreach (int x from 1 to 8)のような構文を思いついた場合は、おそらくforループを改善することに同意するでしょう。しかしながら、 Enumerable.Rangeはかなり不格好です。

49
mqp

これはただの楽しみです。 (標準の「for (int i = 1; i <= 10; i++)」ループ形式を自分で使用するだけです。)

foreach (int i in 1.To(10))
{
    Console.WriteLine(i);    // 1,2,3,4,5,6,7,8,9,10
}

// ...

public static IEnumerable<int> To(this int from, int to)
{
    if (from < to)
    {
        while (from <= to)
        {
            yield return from++;
        }
    }
    else
    {
        while (from >= to)
        {
            yield return from--;
        }
    }
}

Step拡張メソッドを追加することもできます:

foreach (int i in 5.To(-9).Step(2))
{
    Console.WriteLine(i);    // 5,3,1,-1,-3,-5,-7,-9
}

// ...

public static IEnumerable<T> Step<T>(this IEnumerable<T> source, int step)
{
    if (step == 0)
    {
        throw new ArgumentOutOfRangeException("step", "Param cannot be zero.");
    }

    return source.Where((x, i) => (i % step) == 0);
}
38
LukeH

C#6.0での使用

using static System.Linq.Enumerable;

あなたはそれを単純化することができます

foreach (var index in Range(1, 7))
{
    Console.WriteLine(index);
}
12
Mike Tsayper

すでに解決された問題に対する非常に長いアプローチのようです。本当に必要ないEnumerable.Rangeの背後には、ステートマシン全体があります。

従来の形式は開発の基本であり、すべての人になじみのあるものです。あなたの新しいスタイルには何の利点もありません。

9
spender

実際にこれをC#で行うことができます(ToIEnumerable<T>の拡張メソッドとしてそれぞれDointを提供することにより):

1.To(7).Do(Console.WriteLine);

Smalltalk永遠に!

9
THX-1138

私はそのアイデアが好きです。 Pythonによく似ています。数行の私のバージョンを次に示します。

_static class Extensions
{
    public static IEnumerable<int> To(this int from, int to, int step = 1) {
        if (step == 0)
            throw new ArgumentOutOfRangeException("step", "step cannot be zero");
        // stop if next `step` reaches or oversteps `to`, in either +/- direction
        while (!(step > 0 ^ from < to) && from != to) {
            yield return from;
            from += step;
        }
    }
}
_

Pythonのように動作します:

  • 0.To(4)→_[ 0, 1, 2, 3 ]_
  • 4.To(0)→_[ 4, 3, 2, 1 ]_
  • 4.To(4)→_[ ]_
  • 7.To(-3, -3)→_[ 7, 4, 1, -2 ]_
6
Kache

Foreach + Enumerable.Rangeの方がエラーが発生しにくいと思います(ループが終了しないようにボディ内のインデックスを減らすなど、コントロールと間違った方法が少なくなります)

読みやすさの問題は、1つの言語から別の言語に変更できるRange関数のセマンティクスに関するものです(たとえば、1つのパラメーターのみを指定すると、0または1から始まるか、終了が含まれるか除外されるか、2番目のパラメーターはカウントではなく終了です)値)。

パフォーマンスについては、コンパイラは両方のループを最適化できるほど十分にスマートである必要があると思うので、大きな範囲でも同様の速度で実行されます(Rangeはコレクションを作成せず、もちろんイテレータを作成します)。

6
fortran

Rangeはインラインで範囲を操作するのに便利だと思います:

var squares = Enumerable.Range(1, 7).Select(i => i * i);

それぞれオーバーできます。リストに変換する必要がありますが、必要に応じて物事をコンパクトに保ちます。

Enumerable.Range(1, 7).ToList().ForEach(i => Console.WriteLine(i));

しかし、このようなもの以外では、従来のforループを使用します。

5
mcNux

Python、Haskellなどのような他のいくつかの言語の構文を持ちたいです。

// Write the numbers 1 thru 7
foreach (int index in [1..7])
{
    Console.WriteLine(index);
}

幸いなことに、F#が手に入りました:)

C#については、Enumerable.Range 方法。

5
Thomas Danecker

@Luke:To()拡張メソッドを再実装し、Enumerable.Range()メソッドを使用して実行しました。この方法では、少し短くなり、.NETによって提供されるインフラストラクチャを可能な限り使用します。

public static IEnumerable<int> To(this int from, int to)
{ 
    return from < to 
            ? Enumerable.Range(from, to - from + 1) 
            : Enumerable.Range(to, from - to + 1).Reverse();
}
5
Thorsten Lorenz

私は誰もが個人的な好みを持っていると確信しています(ほとんどすべてのプログラミング言語に精通しているという理由だけで後者を好む人は多いでしょう) 。

3
TheTXI

私の意見では、Enumerable.Range()の方法はより宣言的です。新しくて不慣れな人はいますか?もちろん。しかし、この宣言的なアプローチは、他のほとんどのLINQ関連の言語機能と同じ利点をもたらすと思います。

2
MEMark

パラメーターの式を処理する場合、特にその式の値の一部がループ内で変更される場合、Enumerable.Range(index, count)がより明確になるシナリオがあると思います。 forの場合、式は現在の反復後の状態に基づいて評価されますが、Enumerable.Range()は事前に評価されます。

それ以外は、forにこだわる方が通常は良いと思うことに同意します(より多くの人に馴染みやすい/読みやすい...読みやすいは、維持する必要があるコードで非常に重要な値です)。

2
jerryjvl

今日新しい構文を使用する方法

この質問のため、一流の言語サポートを待たずにニース構文を思いつくためにいくつかのことを試しました。ここに私が持っているものがあります:

using static Enumerizer;

// prints: 0 1 2 3 4 5 6 7 8 9
foreach (int i in 0 <= i < 10)
    Console.Write(i + " ");

<=<の違いではありません。

GitHubの概念実証リポジトリを作成し、さらに多くの機能を追加しました(反復の繰り返し、カスタムステップサイズ)。

上記のループの最小限で非常に限られた実装は、次のようになります。

public readonly struct Enumerizer
{
    public static readonly Enumerizer i = default;

    public Enumerizer(int start) =>
        Start = start;

    public readonly int Start;

    public static Enumerizer operator <(int start, Enumerizer _) =>
        new Enumerizer(start);

    public static Enumerizer operator >(int _, Enumerizer __) =>
        throw new NotImplementedException();

    public static IEnumerable<int> operator <=(Enumerizer start, int end)
    {
        for (int i = start.Start; i < end; i++)
            yield return i;
    }

    public static IEnumerable<int> operator >=(Enumerizer _, int __) =>
        throw new NotImplementedException();
}
1
Bruno Zell

多くの(またはほとんどの場合)foreachは、単純にコレクションを反復処理するときに、標準のfor- loopよりも読みやすいことに同意します。ただし、Enumerable.Range(index, count)を使用する選択は、foreach overの値の強力な例ではありません。

1, Enumerable.Range(index, count)から始まる単純な範囲の場合、非常に読みやすくなります。ただし、範囲が異なるインデックスで始まる場合は、index + count - 1を適切に実行して最後の要素が何であるかを判断する必要があるため、読みにくくなります。例えば…

// Write the numbers 2 thru 8
foreach (var index in Enumerable.Range( 2, 7 ))
{
    Console.WriteLine(index);
}

この場合、2番目の例を非常に好みます。

// Write the numbers 2 thru 8
for (int index = 2; index <= 8; index++)
{
    Console.WriteLine(index);
}
1
Dustin Campbell

厳密に言えば、列挙を誤用しています。

列挙子は、コンテナ内のすべてのオブジェクトに1つずつアクセスする手段を提供しますが、順序を保証するものではありません。

列挙を使用して配列内の最大数を見つけることは問題ありません。たとえば、最初の非ゼロ要素を見つけるためにこれを使用している場合、知らないはずの実装の詳細に依存しています。あなたの例では、順序はあなたにとって重要なようです。

編集:私は間違っています。 Lukeが指摘したように(コメントを参照)、C#で配列を列挙するときに順序に依存しても安全です。これは、たとえば、JavaScriptで配列を列挙するために「for in」を使用することとは異なります。

1
buti-oxa

foreach + Enumerable.Rangeアプローチし、時々それを使用します。

// does anyone object to the new style over the traditional style?
foreach (var index in Enumerable.Range(1, 7))

私はあなたの提案のvar乱用に反対します。 varに感謝しますが、この場合はintと書いてください! ;-)

0
xyz