[C#] foreach 루프의 현재 반복 색인을 어떻게 얻습니까?

foreach 루프의 현재 반복을 나타내는 값을 얻기 위해 C #에서 내가 경험하지 않은 희귀 언어 구조 (최근에 배운 소수, 스택 오버플로에 대한 일부)가 있습니까?

예를 들어, 현재 상황에 따라 이와 같은 작업을 수행합니다.

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}



답변

foreach구현 컬렉션을 통해 반복입니다 IEnumerable. GetEnumerator컬렉션 을 호출하여이 작업 을 수행하면을 반환합니다 Enumerator.

이 열거 자에는 메소드와 속성이 있습니다.

  • MoveNext ()
  • 흐름

Current열거자가 현재있는 개체를 반환하고 다음 개체로 MoveNext업데이트 Current합니다.

인덱스 개념은 열거 개념과는 다른 개념이므로 수행 할 수 없습니다.

이 때문에 대부분의 컬렉션은 인덱서와 for 루프 구문을 사용하여 순회 할 수 있습니다.

이 상황에서 로컬 변수로 색인을 추적하는 것과 비교하여 for 루프를 사용하는 것이 좋습니다.


답변

Ian Mercer는 Phil Haack의 블로그 에 이와 유사한 솔루션을 게시했습니다 .

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

아이템을 얻습니다 (item.value LINQ의 오버로드를item.i 사용하여 )과 색인 ( )을 .Select

[inside Select] 함수의 두 번째 매개 변수는 소스 요소의 인덱스를 나타냅니다.

그만큼 new { i, value } 새로운 창조하고 익명의 개체를 .

다음을 사용하여 힙 할당을 피할 수 있습니다. ValueTupleC # 7.0 이상을 사용 경우 .

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

item.자동 파괴를 사용하여를 제거 할 수도 있습니다 .

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>


답변

마지막으로 C # 7에는 foreach루프 (예 : 튜플) 내부에 인덱스를 가져 오는 적절한 구문이 있습니다 .

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

약간의 확장 방법이 필요합니다.

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
   => self.Select((item, index) => (item, index)); 


답변

다음과 같이 할 수 있습니다 :

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}


답변

for대부분의 경우 루프가 더 나은 선택 이라는 의견에 동의하지 않습니다 .

foreach유용한 구문이며 for모든 상황에서 루프로 대체 할 수 없습니다 .

예를 들어 DataReader가 있고 foreach이를 사용하여 모든 레코드를 반복하는 경우 자동으로 Dispose를 호출합니다. 메소드를 하고 리더를 닫습니다 (연결을 자동으로 닫을 수 있음). 따라서 리더를 닫는 것을 잊어 버린 경우에도 연결 누출을 방지하므로 더 안전합니다.

(항상 독자를 닫는 것이 좋지만 컴파일러가 그렇지 않은 경우 컴파일러가이를 포착하지는 않습니다. 모든 독자를 닫았다 고 보장 할 수는 없지만 연결을 통해 누수가 발생하지 않도록 할 수는 있습니다) foreach를 사용하는 습관.)

Dispose유용한 메소드 의 내재적 호출에 대한 다른 예가있을 수 있습니다 .


답변

리터럴 답변-경고, 성능은 int인덱스를 추적 하는 데 사용하는 것만 큼 좋지 않을 수 있습니다 . 적어도를 사용하는 것보다 낫습니다 IndexOf.

컬렉션의 각 항목을 인덱스를 알고있는 익명의 개체로 감싸려면 Select의 인덱싱 오버로드를 사용해야합니다. 이것은 IEnumerable을 구현하는 모든 것에 대해 수행 될 수 있습니다.

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}


답변

LINQ, C # 7 및 System.ValueTupleNuGet 패키지를 사용하여 다음을 수행 할 수 있습니다.

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

일반 foreach구문을 사용하고 개체의 구성원이 아닌 값과 인덱스에 직접 액세스 할 수 있으며 두 필드를 모두 루프 범위에만 유지합니다. 이러한 이유로 C # 7 및을 사용할 수 있다면 이것이 최선의 해결책이라고 생각합니다 System.ValueTuple.