문제

foreach 루프의 현재 반복을 나타내는 값을 얻기 위해 C#에서 아직 접하지 못한 몇 가지 희귀한 언어 구문(최근에 배운 몇 가지, 스택 오버플로에 대한 일부)이 있습니까?

예를 들어 저는 현재 상황에 따라 다음과 같은 작업을 수행합니다.

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
도움이 되었습니까?

해결책

그만큼 foreach 구현하는 컬렉션을 반복하는 것입니다. IEnumerable.이 작업은 호출을 통해 수행됩니다. GetEnumerator 컬렉션에서 Enumerator.

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

  • 이동다음()
  • 현재의

Current Enumerator가 현재 켜져 있는 객체를 반환합니다. 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) 및 해당 인덱스(item.i) 사용하여 Linq의 과부하 Select:

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

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

다음을 사용하여 힙 할당을 피할 수 있습니다. ValueTuple C# 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>

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

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

마지막으로 C#7에는 인덱스를 가져오는 데 적합한 구문이 있습니다. foreach 루프(i.이자형.튜플):

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

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

foreach 유용한 구성이며 대체할 수 없습니다. for 모든 상황에서 루프를 실행하세요.

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

(물론 항상 판독기를 닫는 것이 좋은 습관이지만 그렇게 하지 않으면 컴파일러가 이를 포착하지 못할 것입니다. 모든 판독기를 닫았다고 보장할 수는 없지만 다음을 통해 연결이 누출되지 않을 가능성을 높일 수 있습니다. 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);
}

@FlySwat의 답변을 사용하여 다음 솔루션을 생각해냈습니다.

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

다음을 사용하여 열거자를 얻습니다. GetEnumerator 그런 다음 for 고리.그러나 비결은 루프의 조건을 만드는 것입니다. listEnumerator.MoveNext() == true.

이후 MoveNext 열거자의 메서드는 다음 요소가 있고 액세스할 수 있는 경우 true를 반환하므로 반복할 요소가 부족할 때 루프 조건에 따라 루프가 중지됩니다.

LINQ, C# 7 및 System.ValueTuple NuGet 패키지에서는 다음을 수행할 수 있습니다.

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

정규식을 이용하시면 됩니다 foreach 객체의 멤버가 아닌 값과 인덱스를 직접 구성하고 액세스할 수 있으며 두 필드를 루프 범위 내에서만 유지합니다.이러한 이유로 C# 7을 사용할 수 있고 System.ValueTuple.

원래 열거자를 인덱스 정보가 포함된 다른 열거자로 래핑할 수 있습니다.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

다음은 ForEachHelper 수업.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

카운터 변수를 사용하는 데에는 아무런 문제가 없습니다.사실, 당신이 사용하든 for, foreach while 또는 do, 카운터 변수는 어딘가에서 선언되고 증가되어야 합니다.

따라서 적절하게 색인화된 컬렉션이 있는지 확실하지 않은 경우 다음 관용구를 사용하세요.

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

그렇지 않으면 이것을 사용하십시오 알다 당신의 인덱싱 가능 인덱스 액세스의 경우 컬렉션은 O(1)입니다. Array 그리고 아마도 List<T> (문서에는 나와 있지 않지만) 다른 유형(예: LinkedList)):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

절대로 '수동으로' 작동할 필요가 없어야 합니다. IEnumerator 호출하여 MoveNext() 심문하고 Current - foreach 당신의 특별한 귀찮은 일을 덜어주고 있습니다 ...항목을 건너뛰어야 하는 경우에는 continue 루프의 본문에서.

그리고 완전성을 위해, 당신이 어떤 사람이었는지에 따라 행위 인덱스를 사용하면(위 구성은 많은 유연성을 제공함) Parallel LINQ를 사용할 수 있습니다.

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

우리는 사용 AsParallel() 위의 내용은 이미 2014년이고 우리는 이러한 다중 코어를 잘 활용하여 작업 속도를 높이고 싶기 때문입니다.또한 '순차적' LINQ의 경우 당신은 단지 ForEach() 확장 방법 List<T> 그리고 Array ...그리고 그것을 사용하는 것이 간단한 작업을 수행하는 것보다 낫다는 것은 확실하지 않습니다. foreach, 더 추악한 구문을 위해 여전히 단일 스레드를 실행하고 있기 때문입니다.

이 문제에 대해 제가 방금 생각해낸 해결책은 다음과 같습니다.

원본 코드:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

업데이트된 코드

enumerable.ForEach((item, index) => blah(item, index));

확장 방법:

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

이는 다음을 지원하는 컬렉션에서 작동합니다. IList.

C# 7은 마침내 이를 수행하는 우아한 방법을 제공합니다.

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

IEnumerable이 아닌 List에 대해서만 작동하지만 LINQ에는 다음이 있습니다.

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@Jonathan 나는 그것이 훌륭한 답변이라고 말하지 않았고 단지 그가 요청한 것을 수행하는 것이 가능하다는 것을 보여 주었다고 말했습니다 :)

@Graphain 나는 그것이 빠를 것이라고 기대하지 않습니다. 그것이 어떻게 작동하는지 완전히 확신하지 못합니다. 매번 전체 목록을 반복하여 일치하는 개체를 찾을 수 있습니다. 이는 엄청나게 많은 비교가 될 것입니다.

즉, List는 개수와 함께 각 개체의 인덱스를 유지할 수 있습니다.

조나단이 더 자세히 설명한다면 더 좋은 생각이 있는 것 같나요?

하지만 foreach에서 현재 위치를 계산하는 것이 더 간단하고 적응력이 더 좋습니다.

자신만의 색인을 추가하기만 하면 됩니다.간단하게 유지하세요.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

이것이 제가 하는 방법입니다. 이는 단순성/간결성 때문에 좋지만 루프 본문에서 많은 작업을 수행하는 경우 obj.Value, 꽤 빨리 늙어 갈 것입니다.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

왜 foreach?!

가장 간단한 방법은 ~을 위한 foreach 대신 목록을 사용하는 경우 .

for(int i = 0 ; i < myList.Count ; i++)
{
    // Do Something...
}

또는 foreach를 사용하려면 다음을 수행하세요.

foreach (string m in myList)
{
     // Do something...       
}

이것을 사용하여 각 루프의 인덱스를 알 수 있습니다.

myList.indexOf(m)

키워드를 사용하는 것이 더 좋습니다 continue 이렇게 안전한 공사를 하세요

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

컬렉션이 목록인 경우 다음과 같이 List.IndexOf를 사용할 수 있습니다.

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

주요 답변은 다음과 같습니다.

"분명히 인덱스 개념은 열거 개념과 낯설기 때문에 수행할 수 없습니다."

이는 현재 C# 버전에도 해당되지만 개념적 제한은 아닙니다.

MS가 새로운 C# 언어 기능을 개발하면 새로운 인터페이스 IIndexedEnumerable에 대한 지원과 함께 이 문제가 해결될 수 있습니다.

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

foreach가 IEnumerable을 전달하고 IIndexedEnumerable을 확인할 수 없지만 var 인덱스를 사용하여 요청하는 경우 C# 컴파일러는 인덱스 추적을 위한 코드를 추가하는 IndexedEnumerable 개체로 소스를 래핑할 수 있습니다.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

왜:

  • Foreach는 더 좋아 보이고 비즈니스 애플리케이션에서는 성능 병목 현상이 거의 발생하지 않습니다.
  • Foreach는 메모리 측면에서 더 효율적일 수 있습니다.각 단계에서 새 컬렉션으로 변환하는 대신 함수 파이프라인을 사용합니다.CPU 주기를 몇 개 더 사용하는지, CPU 캐시 오류가 적고 GC.Collects가 적은지 누가 신경쓰나요?
  • 코더에게 색인 추적 코드를 추가하도록 요구하면 아름다움이 손상됩니다.
  • 구현하기가 매우 쉽고(MS 덕분에) 이전 버전과 호환됩니다.

여기 있는 대부분의 사람들은 MS는 아니지만 이것이 정답이며 MS에 로비하여 이러한 기능을 추가할 수 있습니다.이미 다음을 사용하여 자신만의 반복자를 만들 수 있습니다. 확장 함수 및 튜플 사용, 그러나 MS는 확장 기능을 피하기 위해 구문 설탕을 뿌릴 수 있습니다.

이 문제에 대한 나의 해결책은 확장 방법입니다. WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

다음과 같이 사용하세요

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

관심을 끌기 위해 Phil Haack은 Razor 템플릿 위임(http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx)

효과적으로 그는 반복 중에 인덱스와 요소에 대한 액세스를 허용하는 "IteratedItem" 클래스(아래 참조)에서 반복을 래핑하는 확장 메서드를 작성합니다.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

그러나 Razor가 아닌 환경에서는 단일 작업(예:람다로 제공될 수 있는 것) Razor가 아닌 컨텍스트에서 for/foreach 구문을 확실히 대체하지는 않습니다.

나는 이것이 매우 효율적이라고 생각하지 않지만 작동합니다.

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

나는 이것을 내장했다 LINQPad:

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

그냥 사용할 수도 있습니다 string.join:

var joinResult = string.Join(",", listOfNames);

다음과 같이 루프를 작성할 수 있습니다.

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

다음 구조체와 확장 메서드를 추가한 후.

구조체 및 확장 메서드는 Enumerable.Select 기능을 캡슐화합니다.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}

foreach 루프의 현재 반복 값을 얻을 수 있는 방법이 없다고 생각합니다.스스로 계산하는 것이 가장 좋은 방법인 것 같습니다.

왜 알고 싶어하는지 물어봐도 될까요?

다음 세 가지 중 하나를 수행할 가능성이 가장 높은 것 같습니다.

1) 컬렉션에서 개체를 가져오지만 이 경우에는 이미 개체를 가지고 있습니다.

2) 나중에 사후 처리를 위해 개체 수를 계산합니다. 컬렉션에는 사용할 수 있는 Count 속성이 있습니다.

3) 루프의 순서에 따라 개체의 속성을 설정합니다. 컬렉션에 개체를 추가할 때 쉽게 설정할 수 있지만.

컬렉션이 일부 메서드를 통해 개체의 인덱스를 반환할 수 없는 경우 유일한 방법은 예제와 같은 카운터를 사용하는 것입니다.

그러나 인덱스로 작업할 때 문제에 대한 유일한 합리적인 대답은 for 루프를 사용하는 것입니다.시간과 공간의 복잡성은 말할 것도 없고 다른 어떤 것도 코드를 복잡하게 만듭니다.

이런 건 어때요?myEnumerable이 비어 있으면 myDelimitedString이 null일 수 있습니다.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

방금 이 문제가 발생했지만 제 경우의 문제를 생각하면 예상한 솔루션과 관련이 없는 최상의 솔루션이 나왔습니다.

이는 매우 일반적인 경우일 수 있습니다. 기본적으로 하나의 소스 목록을 읽고 이를 기반으로 대상 목록에 객체를 생성합니다. 그러나 먼저 소스 항목이 유효한지 확인하고 임의의 행을 반환하려고 합니다. 오류.얼핏 보면 Current 속성에 있는 개체의 열거자에 대한 인덱스를 가져오고 싶지만 이러한 요소를 복사할 때 어쨌든 현재 대상에서 현재 인덱스를 암시적으로 알 수 있습니다.분명히 이는 대상 개체에 따라 다르지만 나에게 그것은 목록이었고 아마도 ICollection을 구현할 가능성이 높습니다.

즉.

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

항상 적용 가능한 것은 아니지만 언급할 가치가 있는 경우가 많다고 생각합니다.

어쨌든, 요점은 때때로 당신이 가지고 있는 논리에 이미 명확하지 않은 해결책이 있다는 것입니다...

질문에 기초한 색인 정보로 무엇을 하려는지 잘 모르겠습니다.그러나 C#에서는 일반적으로 IEnumerable.Select 메서드를 적용하여 원하는 항목에서 인덱스를 가져올 수 있습니다.예를 들어, 값이 홀수인지 짝수인지에 대해 이와 같은 것을 사용할 수 있습니다.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

그러면 항목이 목록에서 홀수(1)인지 짝수(0)인지 이름별로 사전이 제공됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top