C # foreach 개선?


9

나는 foreach 내부에 루프 카운트 인덱스를 갖고 정수를 만들고, 사용하고, 증분하는 등의 프로그래밍을하는 동안이 문제를 자주 겪습니다. foreach 내부의 루프 카운트? 다른 루프에서도 사용할 수 있습니다.

컴파일러 가이 키워드를 어디서나 루프 구문에서 사용하도록 허용하지 않는지 키워드 사용에 반대합니까?


7
확장 방법으로 직접 할 수 있습니다. Dan Finch의 답변 참조 : stackoverflow.com/a/521894/866172 (최고의 답변은 아니지만 "깨끗한"답변)
Jalayn

펄과 $ _ 같은 것들이 떠 오릅니다. 나는 그런 것들이 싫어.
얌 마르코비치

2
C #에 대해 알고있는 것에는 문맥 기반 키워드가 많으므로 "키워드 사용에 반대하지 않습니다". 그러나 아래 답변에서 지적했듯이 for () 루프 를 사용하고 싶을 것입니다 .
Craige

@Jalayn 답변으로 게시하십시오. 가장 좋은 방법 중 하나입니다.
Apoorv Khurasia 2018 년

@MonsterTruck : 나는 그가해야한다고 생각하지 않습니다. 실제 해결책은이 질문을 SO로 마이그레이션하고 Jalayn이 연결하는 질문의 복제본으로 닫는 것입니다.
Brian

답변:


54

루프 내부에 루프 카운트가 필요한 경우 foreach일반 for루프를 사용하지 마십시오 . foreach루프의 특정 사용하기 위해 의도 된 for단순 루프를. 의 단순성 foreach이 더 이상 도움이되지 않는 상황이있는 것처럼 들립니다 .


1
+1-좋은 지적. IEnumerable.Count 또는 object.length를 규칙적인 for 루프와 함께 사용하면 객체의 요소 수를 파악하는 데 문제가 발생하지 않습니다.

11
@ GlenH7 : 구현에 따라 IEnumerable.Count비용이 많이들 수 있습니다 (한 번만 열거하는 경우 불가능). 안전하게하기 전에 기본 소스가 무엇인지 알아야합니다.
이진 걱정

2
-1 : 빌 와그너 더 효과적인 C #을 하나가 항상 충실해야하는 이유를 설명합니다 foreach이상 for (int i = 0…). 그는 몇 페이지를 작성했지만 반복에 대한 컴파일러 최적화라는 두 단어로 요약 할 수 있습니다. 좋아, 그것은 두 단어가 아니었다.
Apoorv Khurasia 2014 년

13
@MonsterTruck : Bill Wagner가 작성한 것이 무엇이든, for루프가 더 읽기 쉬운 코드를 제공 하는 이론적 인 OP와 같은 상황을 보았습니다 (이론적 컴파일러 최적화는 종종 조기 최적화입니다).
Doc Brown

1
@DocBrown 이것은 조기 최적화만큼 간단하지 않습니다. 나는 과거에 병목 현상을 식별 하고이 접근법을 사용하여 병목 현상을 해결했습니다 (그러나 컬렉션의 수천 가지 객체를 처리하고 있음을 인정합니다). 곧 실례를 게시하기를 바랍니다. 한편, 여기에 Jon Skeet이 있습니다. [성능 향상은 크지 않지만 컬렉션과 개체 수에 따라 크게 달라집니다.]
Apoorv Khurasia 2016 년

37

이런 익명의 유형을 사용할 수 있습니다

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

이것은 파이썬의 enumerate기능 과 유사 합니다

for i, v in enumerate(items):
    print v, i

+1 Oh. 전구. 나는 그 접근법을 좋아한다. 내가 왜 그런 생각을 한 적이 없는가?
Phil

17
C #에서 이것을 고전적인 for 루프보다 선호하는 사람은 가독성에 대해 이상한 의견을 가지고 있습니다. 이것은 깔끔한 트릭 일 수 있지만 프로덕션 품질 코드에서는 이와 같은 것을 허용하지 않습니다. 그것은 고전적인 for 루프보다 훨씬 시끄럽고 빠른 것으로 이해 될 수 없습니다.
팔콘

4
@Falcon, 이것은 당신 이 구식 for 루프를 사용할 없다면 유효한 대안입니다 (카운트가 필요합니다). 즉 ... 나는 주위에 일해야했다 몇 개체의 컬렉션입니다
파브 아라 우호

8
@ FabricioAraujo : 분명히 C # for 루프에는 카운트가 필요하지 않습니다. 내가 아는 한 C for 루프와 동일합니다. 즉, 부울 값을 사용하여 루프를 종료 할 수 있습니다. MoveNext가 false를 반환 할 때 열거의 끝을 확인하는 등의 작업입니다.
Zan Lynx

1
이것은 foreach 블록의 시작 부분에서 증분을 찾아야하는 대신 모든 코드를 처리하는 이점이 있습니다.
JeffO

13

요청에 따라 Dan Finch의 답변을 여기에 추가하고 있습니다.

나에게 포인트를주지 말고 Dan Finch에게 포인트를주십시오 :-)

해결책은 다음과 같은 일반적인 확장 방법을 작성하는 것입니다.

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

다음과 같이 사용됩니다.

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
꽤 영리하지만, 정규 코드를 사용하는 것이 더 읽기 쉽다고 생각합니다. 특히 코드를 유지 관리하는 사람이 .NET의 에이스가 아니며 확장 방법의 개념에 익숙하지 않은 경우 특히 그렇습니다. . 그래도 여전히이 작은 코드를 잡고 있습니다. :)
MetalMikester 2016 년

1
이것의 어느 쪽이든 논쟁 할 수 있고, 원래의 질문-나는 포스터 의도에 동의합니다. 나쁘게 필요-내 이름에는 다른 이름을 가진 정확히 같은 솔루션이 있습니다-strings.ApplyIndexed <T> (......)
Mark Mullin

Mark Mullin에 동의합니다. 나는 그것이 확장 방법을 꽤 영리하게 사용한다고 생각했고 질문을 맞았습니다.
Jalayn

5

나는 이것에 대해 틀릴 수도 있지만, 항상 foreach 루프의 핵심은 C ++의 STL 시절부터 반복을 단순화하는 것이라고 생각했습니다.

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

이것을 foreach에서 .NET의 IEnumerable 사용과 비교하십시오.

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

후자는 훨씬 간단하고 많은 오래된 함정을 피합니다. 또한 그들은 거의 동일한 규칙을 따르는 것 같습니다. 예를 들어 루프 내부에서 IEnumerable 내용을 변경할 수 없습니다 (STL 방식으로 생각하면 훨씬 더 의미가 있습니다). 그러나 for 루프는 종종 반복과 다른 논리적 목적을 가지고 있습니다.


4
+1 : foreach는 기본적으로 반복자 패턴에 대한 구문 설탕이라는 것을 기억하는 사람은 거의 없습니다.
Steven Evers

1
약간의 차이점이 있습니다. .NET에 존재하는 포인터 조작은 프로그래머에게 아주 깊이 숨겨져 있지만 C ++ 예제와 매우 유사한 for 루프를 작성할 수 있습니다.for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS

@KeithS, 구조화 된 유형이 아닌 .NET에서 만지는 모든 것이 다른 구문 (-> 대신-.) 인 POINTER입니다. 실제로 참조 유형을 메소드에 전달하는 것은 포인터로 전달하는 것과 동일하며 참조로 전달하는 것은 이중 포인터로 전달하는 것과 같습니다. 같은 것. 적어도, 그것이 모국어 인 사람이 C ++가 생각하는 방식입니다. 일종의, 당신은 언어가 구문 적으로 당신의 모국어와 어떻게 관련되어 있는지 생각함으로써 학교에서 스페인어를 배웁니다.
Jonathan Henson 2016 년

* 값 유형이 구조화 된 유형이 아닙니다. 죄송합니다.
Jonathan Henson

2

해당 컬렉션이 인 경우 인덱싱과 함께 IList<T>간단히 사용할 수 있습니다 for.

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

그렇지 않은 경우을 사용할 Select()수 있으며 인덱스를 제공하는 과부하가 있습니다.

그것이 적절하지 않다면 수동으로 색인을 유지하는 것이 간단하다고 생각합니다. 두 줄의 코드입니다. 이를 위해 특별히 키워드를 작성하는 것은 과도한 일입니다.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

당신은 루프 내에서 정확히 한 번만 인덱스를 사용하는 경우 1도, 당신은 같은 것을 할 수 foo(++i)또는 foo(i++)인덱스가 필요에 따라합니다.
Job

2

컬렉션이 IEnumerable이라는 것을 알고 있지만 지금까지 처리 한 요소 수 (따라서 완료된 총계)를 추적해야하는 경우 기본 for 루프에 몇 줄을 추가 할 수 있습니다.

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

foreach에 증가 된 색인 변수를 추가 할 수도 있습니다.

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

보다 우아하게 보이게하려면 다음 방법을 정의하여 다음 세부 사항을 "숨길"수 있습니다.

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

범위 지정은 다른 이름과의 충돌을 블록에서 깨끗하게 유지하려는 경우에도 작동합니다 ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

이를 위해 while루프를 사용합니다 . 루프 for도 작동하며 많은 사람들이 선호하는 것처럼 보이지만 for고정 된 반복 횟수를 가진 무언가 에만 사용 하고 싶습니다 . IEnumerable런타임에 생성 될 수 있으므로 내 생각에 while더 합리적입니다. 원한다면 비공식적 인 코딩 지침이라고 부릅니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.