UFO ET IT

Enumerable.Range와 기존 for 루프를 사용한 foreach에 대한 생각

ufoet 2020. 11. 29. 12:40
반응형

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'이 작기 때문에 성능이 문제가되지 않는다고 가정하면 기존 스타일보다 새로운 스타일에 반대하는 사람이 있습니까?


이 목적을 위해 후자의 "최소-최대"형식이 Range"최소 카운트"스타일 보다 훨씬 더 명확 합니다. 또한 더 빠르지도, 더 짧지도 않고, 더 친숙하지도 않고, 명확하지도 않은 표준에서 이와 같은 변화를 만드는 것은 좋은 습관이라고 생각하지 않습니다.

즉, 나는 일반적으로 아이디어에 반대하지 않습니다. foreach (int x from 1 to 8)다음 과 같은 구문으로 나에게 다가왔다 면 아마도 그것이 for루프 보다 개선 될 것이라는 데 동의 할 것입니다 . 그러나 Enumerable.Range꽤 투박합니다.


이것은 단지 재미를위한 것입니다. (저는 표준 " 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);
}

C # 6.0에서

using static System.Linq.Enumerable;

당신은 그것을 단순화 할 수 있습니다

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

실제로 C #에서이 작업을 수행 할 수 있습니다 ( 각각 확장 메서드를 제공 To하여 ).DointIEnumerable<T>

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

영원히 SmallTalk!


이미 해결 된 문제에 대한 상당히 긴 접근 방식 인 것 같습니다. Enumerable.Range실제로 필요하지 않은 전체 상태 머신이 뒤에 있습니다.

전통적인 형식은 개발의 기본이며 모두에게 친숙합니다. 나는 당신의 새로운 스타일에 어떤 이점도 보이지 않습니다.


나는 foreach + Enumerable.Range가 오류가 덜 발생한다고 생각합니다 (몸체 내부의 인덱스를 줄여 루프가 끝나지 않도록하는 것과 같이 제어가 적고 잘못하는 방법이 적습니다.)

가독성 문제는 한 언어에서 다른 언어로 변경 될 수있는 Range 함수 의미론에 관한 것입니다 (예 : 하나의 매개 변수 만 주어지면 0 또는 1에서 시작하거나 끝이 포함되거나 제외되거나 두 번째 매개 변수가 끝이 아닌 개수입니다. 값).

성능에 관해서는 컴파일러가 두 루프를 모두 최적화 할 수있을만큼 똑똑해야한다고 생각합니다. 따라서 큰 범위에서도 비슷한 속도로 실행됩니다 (Range는 컬렉션을 생성하지 않지만 물론 반복 자라고 가정합니다).


아이디어가 마음에 듭니다. 파이썬과 매우 비슷합니다. 다음은 몇 줄로 된 내 버전입니다.

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;
        }
    }
}

파이썬처럼 작동합니다 :

  • 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 ]

Python, Haskell 등과 같은 다른 언어의 구문을 갖고 싶습니다.

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

다행히 이제 F #이 생겼습니다. :)

C #의 경우 Enumerable.Range방법 을 고수해야합니다 .


Range는 일부 범위 인라인 작업에 유용하다고 생각합니다.

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

각각 끝낼 수 있습니다. 목록으로 변환해야하지만 원하는 경우 간결하게 유지합니다.

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

그러나 이와 같은 것을 제외하고는 전통적인 for 루프를 사용합니다.


@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();
}

I'm sure everybody has their personal preferences (many would prefer the later just because it is familiar over almost all programming languages), but I am like you and starting to like the foreach more and more, especially now that you can define a range.


I imagine there could be scenarios where Enumerable.Range(index, count) is clearer when dealing with expressions for the parameters, especially if some of the values in that expression are altered within the loop. In the case of for the expression would be evaluated based on the state after the current iteration, whereas Enumerable.Range() is evaluated up-front.

Other than that, I'd agree that sticking with for would normally be better (more familiar/readable to more people... readable is a very important value in code that needs to be maintained).


In my opinion the Enumerable.Range() way is more declarative. New and unfamiliar to people? Certainly. But I think this declarative approach yields the same benefits as most other LINQ-related language features.


Strictly speaking, you misuse enumeration.

Enumerator provides the means to access all the objects in a container one-by-one, but it does not guarantee the order.

It is OK to use enumeration to find the biggest number in an array. If you are using it to find, say, first non-zero element, you are relying on the implementation detail you should not know about. In your example, the order seems to be important to you.

Edit: I am wrong. As Luke pointed out (see comments) it is safe to rely on the order when enumerating an array in C#. This is different from, for example, using "for in" for enumerating an array in Javascript .


I agree that in many (or even most cases) foreach is much more readable than a standard for-loop when simply iterating over a collection. However, your choice of using Enumerable.Range(index, count) isn't a strong example of the value of foreach over for.

For a simple range starting from 1, Enumerable.Range(index, count) looks quite readable. However, if the range starts with a different index, it becomes less readable because you have to properly perform index + count - 1 to determine what the last element will be. For example…

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

In this case, I much prefer the second example.

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

How to use a new syntax today

Because of this question I tried out some things to come up with a nice syntax without waiting for first-class language support. Here's what I have:

using static Enumerizer;

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

Not the difference between <= and <.

I also created a proof of concept repository on GitHub with even more functionality (reversed iteration, custom step size).

A minimal and very limited implementation of the above loop would look something like like this:

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();
}

I do like the foreach + Enumerable.Range approach and use it sometimes.

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

I object to the var abuse in your proposal. I appreciate var, but, damn, just write int in this case! ;-)

참고URL : https://stackoverflow.com/questions/915745/thoughts-on-foreach-with-enumerable-range-vs-traditional-for-loop

반응형