Czy istnieje jakiś rzadki konstrukt języka, którego nie spotkałem (jak kilka, których ostatnio się nauczyłem, niektóre na stosie przepełnienia) w języku C #, aby uzyskać wartość reprezentującą bieżącą iterację pętli foreach?
Na przykład obecnie robię coś takiego w zależności od okoliczności:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
foreach
jest zapewnienie wspólnego mechanizmu iteracji dla wszystkich kolekcji, niezależnie od tego, czy są one indeksowalne (List
), czy nie (Dictionary
).Dictionary
nie można go indeksować, iteracjaDictionary
przechodzi przez niego w określonej kolejności (tzn. Moduł wyliczający jest indeksowany przez to, że dostarcza elementy kolejno). W tym sensie moglibyśmy powiedzieć, że nie szukamy indeksu w kolekcji, a raczej indeksu bieżącego wyliczonego elementu w wyliczeniu (tj. Czy znajdujemy się na pierwszym, piątym czy ostatnim wyliczonym elemencie).Odpowiedzi:
foreach
Jest Iterowanie nad kolekcje implementująceIEnumerable
. Robi to poprzez wywołanieGetEnumerator
kolekcji, która zwróci anEnumerator
.Ten moduł wyliczający ma metodę i właściwość:
Current
zwraca obiekt, na którym aktualnie znajduje się moduł Enumerator,MoveNext
aktualizujeCurrent
następny obiekt.Pojęcie indeksu jest obce koncepcji wyliczenia i nie można tego zrobić.
Z tego powodu większość kolekcji można przeglądać przy użyciu indeksatora i konstrukcji pętli for.
W tej sytuacji zdecydowanie wolę używać pętli for niż śledzenie indeksu za pomocą zmiennej lokalnej.
źródło
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
funkcji Pythona ).Ian Mercer opublikował podobne rozwiązanie na blogu Phila Haacka :
W ten sposób otrzymasz item (
item.value
) i jego indeks (item.i
), korzystając z tego przeciążenia LINQSelect
:new { i, value }
Tworzy nowy obiekt anonimowy .Przydziałów sterty można uniknąć,
ValueTuple
jeśli używasz C # 7.0 lub nowszej wersji:Możesz także wyeliminować
item.
, używając automatycznej destrukcji:źródło
Wreszcie C # 7 ma przyzwoitą składnię do pobierania indeksu wewnątrz
foreach
pętli (tj. Krotek):Potrzebna byłaby mała metoda rozszerzenia:
źródło
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
aby była bardziej rozpoznawalna dla osób przyzwyczajonych do innych języków (i być może zamień też kolejność parametrów krotki). Nie,WithIndex
to i tak nie jest oczywiste.Może zrobić coś takiego:
źródło
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Nie zgadzam się z komentarzami, że
for
pętla jest lepszym wyborem w większości przypadków.foreach
jest użyteczną konstrukcją i nie może być zastąpiona przezfor
pętlę we wszystkich okolicznościach.Na przykład, jeśli masz DataReader i przeglądasz wszystkie rekordy za jego pomocą,
foreach
to automatycznie wywołuje metodę Dispose i zamyka czytnik (który może wtedy automatycznie zamknąć połączenie). Jest to zatem bezpieczniejsze, ponieważ zapobiega wyciekom połączenia, nawet jeśli zapomnisz zamknąć czytnik.(Pewnie, że dobrą praktyką jest zawsze zamykać czytniki, ale kompilator nie złapie tego, jeśli tego nie zrobisz - nie możesz zagwarantować, że zamknąłeś wszystkie czytniki, ale możesz zwiększyć prawdopodobieństwo, że nie wyciekniesz z połączenia przez uzyskanie w zwyczaju używania foreach.)
Mogą istnieć inne przykłady ukrytego wywołania
Dispose
metody, które są przydatne.źródło
foreach
różni sięfor
(i bliżejwhile
) na Programmers.SE .Dosłowna odpowiedź - ostrzeżenie, wydajność może nie być tak dobra, jak zwykłe użycie
int
do śledzenia indeksu. Przynajmniej jest to lepsze niż używanieIndexOf
.Wystarczy użyć przeciążenia indeksowania Select, aby owinąć każdy element w kolekcji anonimowym obiektem znającym indeks. Można to zrobić w stosunku do wszystkiego, co implementuje IEnumerable.
źródło
Korzystając z LINQ, C # 7 i
System.ValueTuple
pakietu NuGet, możesz to zrobić:Możesz użyć zwykłej
foreach
konstrukcji i mieć bezpośredni dostęp do wartości i indeksu, a nie jako element obiektu, i zachowuje oba pola tylko w zakresie pętli. Z tych powodów uważam, że jest to najlepsze rozwiązanie, jeśli możesz używać C # 7 iSystem.ValueTuple
.źródło
Korzystając z odpowiedzi @ FlySwat, wymyśliłem to rozwiązanie:
Otrzymujesz moduł wyliczający za pomocą,
GetEnumerator
a następnie zapętlasz za pomocąfor
pętli. Jednak sztuczka polega na tym, aby ustawić stan pętlilistEnumerator.MoveNext() == true
.Ponieważ
MoveNext
metoda modułu wyliczającego zwraca wartość true, jeśli istnieje kolejny element i można do niego uzyskać dostęp, co powoduje, że warunek pętli powoduje zatrzymanie pętli, gdy zabraknie elementów do iteracji.źródło
IDisposable
.Nie ma nic złego w używaniu zmiennej licznika. W rzeczywistości, niezależnie od tego, czy używasz
for
,foreach
while
czydo
, zmienną licznika należy gdzieś zadeklarować i zwiększyć.Użyj więc tego idiomu, jeśli nie masz pewności, czy masz odpowiednio indeksowaną kolekcję:
W przeciwnym razie skorzystaj z tej, jeśli wiesz, że twoja kolekcja indeksowana ma wartość O (1) w celu uzyskania dostępu do indeksu (dla której będzie
Array
i prawdopodobnieList<T>
(dokumentacja tego nie mówi), ale niekoniecznie dla innych typów (takich jakLinkedList
)):Nigdy nie powinno być konieczne „ręczne” obsługiwanie
IEnumerator
przez wywoływanieMoveNext()
i przesłuchiwanieCurrent
-foreach
to oszczędza ci tego szczególnego kłopotu ... jeśli chcesz pominąć przedmioty, po prostu użyjcontinue
w ciele pętli.I tylko dla kompletności, w zależności od tego, co robiłeś z indeksem (powyższe konstrukcje oferują dużą elastyczność), możesz użyć Parallel LINQ:
Używamy
AsParallel()
powyżej, ponieważ to już 2014 rok i chcemy dobrze wykorzystać te wiele rdzeni, aby przyspieszyć. Ponadto w przypadku „sekwencyjnego” LINQ otrzymujesz tylkoForEach()
metodę rozszerzeniaList<T>
iArray
… i nie jest jasne, że użycie jej jest lepsze niż wykonanie prostejforeach
, ponieważ nadal używasz jednowątkowej składni dla brzydszych.źródło
Możesz owinąć oryginalny moduł wyliczający innym, który zawiera informacje o indeksie.
Oto kod
ForEachHelper
klasy.źródło
Oto rozwiązanie, które właśnie wymyśliłem dla tego problemu
Oryginalny kod:
Zaktualizowany kod
Metoda rozszerzenia:
źródło
Po prostu dodaj własny indeks. Nie komplikuj.
źródło
Będzie działać tylko dla listy, a nie dla dowolnej IEnumerable, ale w LINQ jest to:
@Jathanathan Nie powiedziałem, że to świetna odpowiedź, po prostu powiedziałem, że pokazuje, że można zrobić to, o co prosił :)
@Graphain Nie spodziewałbym się, że będzie szybki - nie jestem do końca pewien, jak to działa, może za każdym razem powtarzać całą listę, aby znaleźć pasujący obiekt, który byłby niezrównanym porównaniem.
To powiedziawszy, List może przechowywać indeks każdego obiektu wraz z liczbą.
Jonathan wydaje się mieć lepszy pomysł, gdyby opracował?
Lepiej byłoby po prostu policzyć, gdzie jesteś na wyciągnięcie ręki, prostsze i bardziej elastyczne.
źródło
C # 7 w końcu daje nam elegancki sposób na zrobienie tego:
źródło
Po co przepowiadać?
Najprostszym sposobem jest użycie do zamiast foreach jeśli używasz listy :
Lub jeśli chcesz użyć foreach:
Możesz użyć tego do poznania indeksu każdej pętli:
źródło
Działa to w przypadku obsługi kolekcji
IList
.źródło
O(n^2)
ponieważ w większości wdrożeńIndexOf
jestO(n)
. 2) Nie powiedzie się, jeśli na liście znajdują się zduplikowane elementy.Tak to robię, co jest miłe ze względu na swoją prostotę / zwięzłość, ale jeśli dużo robisz w ciele pętli
obj.Value
, szybko się zestarzeje.źródło
Ta odpowiedź: lobbować zespół językowy C # w celu uzyskania bezpośredniej pomocy językowej.
Wiodąca odpowiedź brzmi:
Chociaż dotyczy to obecnej wersji językowej C # (2020), nie jest to koncepcyjny limit CLR / język, można to zrobić.
Zespół programistów języka Microsoft C # mógłby stworzyć nową funkcję języka C #, dodając obsługę nowego interfejsu IIndexedEnumerable
Gdyby
foreach ()
jest używany iwith var index
jest obecny, kompilator oczekuje, że kolekcja elementów zadeklarujeIIndexedEnumerable
interfejs. Jeśli interfejs jest nieobecny, kompilator może zapełnić polifile źródło z obiektem IndexedEnumerable, który dodaje kod do śledzenia indeksu.Później można zaktualizować CLR, aby zawierał wewnętrzne śledzenie indeksu, które jest używane tylko wtedy, gdy
with
określono słowo kluczowe, a źródło nie implementuje bezpośrednioIIndexedEnumerable
Dlaczego:
Chociaż większość ludzi tutaj nie jest pracownikami Microsoft, jest to poprawna odpowiedź, możesz lobbować Microsoft, aby dodać taką funkcję. Możesz już zbudować swój iterator z funkcją rozszerzenia i używać krotek , ale Microsoft może posypać cukrem składniowym, aby uniknąć funkcji rozszerzenia
źródło
Jeśli kolekcja jest listą, możesz użyć List.IndexOf, jak w:
źródło
Lepiej używać
continue
takiej bezpiecznej konstrukcji słów kluczowychźródło
Możesz napisać swoją pętlę w następujący sposób:
Po dodaniu następującej struktury i metody rozszerzenia.
Metoda struct i extension zawiera funkcjonalność Enumerable.Select.
źródło
Moim rozwiązaniem tego problemu jest metoda rozszerzenia
WithIndex()
,http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
Użyj tego jak
źródło
struct
dla pary (indeks, element).Dla zainteresowania Phil Haack właśnie napisał przykład tego w kontekście delegata szablonów Razor ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )
W efekcie pisze metodę rozszerzenia, która otacza iterację klasą „IteratedItem” (patrz poniżej), umożliwiając dostęp do indeksu oraz elementu podczas iteracji.
Jednak chociaż byłoby to w porządku w środowisku innym niż Razor, jeśli wykonujesz jedną operację (tj. Taką, która może być podana jako lambda), nie będzie to solidne zastąpienie składni for / foreach w kontekstach innych niż Razor .
źródło
Nie sądzę, że powinno to być dość wydajne, ale działa:
źródło
Zbudowałem to w LINQPad :
Możesz także użyć
string.join
:źródło
n
elementy, wówczas operacje sąn * n
lub sąn
kontynuowane. en.wikipedia.org/wiki/…Nie wierzę, że istnieje sposób na uzyskanie wartości bieżącej iteracji pętli foreach. Liczenie siebie wydaje się najlepszym sposobem.
Czy mogę zapytać, dlaczego chcesz wiedzieć?
Wygląda na to, że najprawdopodobniej zrobiłbyś jedną z trzech rzeczy:
1) Pobieranie obiektu z kolekcji, ale w tym przypadku już go masz.
2) Liczenie obiektów do późniejszego przetwarzania końcowego ... kolekcje mają właściwość Count, z której można skorzystać.
3) Ustawienie właściwości obiektu na podstawie jego kolejności w pętli ... chociaż możesz łatwo ustawić tę właściwość, gdy dodasz obiekt do kolekcji.
źródło
O ile twoja kolekcja nie może zwrócić indeksu obiektu za pomocą jakiejś metody, jedynym sposobem jest użycie licznika jak w twoim przykładzie.
Jednak podczas pracy z indeksami jedyną rozsądną odpowiedzią na problem jest użycie pętli for. Wszystko inne wprowadza złożoność kodu, nie mówiąc już o złożoności czasu i przestrzeni.
źródło
Co powiesz na coś takiego? Zauważ, że myDelimitedString może mieć wartość NULL, jeśli myEnumerable jest pusty.
źródło
Właśnie miałem ten problem, ale zastanowienie się nad tym problemem w moim przypadku dało najlepsze rozwiązanie, niezwiązane z oczekiwanym rozwiązaniem.
To może być dość powszechny przypadek, w zasadzie czytam z jednej listy źródłowej i tworzę obiekty na ich podstawie na liście docelowej, jednak muszę najpierw sprawdzić, czy elementy źródłowe są prawidłowe i chcę zwrócić wiersz dowolnego błąd. Na pierwszy rzut oka chcę wprowadzić indeks do modułu wyliczającego obiekt we właściwości Current, jednak gdy kopiuję te elementy, i tak domyślnie znam bieżący indeks z bieżącego miejsca docelowego. Oczywiście zależy to od docelowego obiektu, ale dla mnie była to lista i najprawdopodobniej zaimplementuje ICollection.
to znaczy
Myślę, że nie zawsze ma zastosowanie, ale wystarczająco często, aby warto było o tym wspomnieć.
W każdym razie chodzi o to, że czasami w logice masz już nieoczywiste rozwiązanie ...
źródło
Nie byłem pewien, co próbujesz zrobić z informacjami indeksu opartymi na pytaniu. Jednak w języku C # zwykle można dostosować metodę IEnumerable.Select, aby uzyskać indeks z dowolnego elementu. Na przykład mogę użyć czegoś takiego do ustalenia, czy wartość jest nieparzysta, czy parzysta.
Dałoby to słownik według nazwy, czy element był nieparzysty (1), a nawet (0) na liście.
źródło