C # przewidywać ulepszenia?

9

Często spotykam się z tym podczas programowania, w którym chcę mieć indeks liczby pętli w foreach i muszę utworzyć liczbę całkowitą, użyć go, zwiększyć itd. Czy nie byłoby dobrym pomysłem, gdyby wprowadzono słowo kluczowe, które było liczba pętli w foreach? Może być również używany w innych pętlach.

Czy stoi to w sprzeczności ze stosowaniem słów kluczowych, ponieważ kompilator nie pozwala na użycie tego słowa kluczowego nigdzie indziej niż w konstrukcji pętli?

Justin
źródło
7
Możesz to zrobić sam, korzystając z metody rozszerzenia, zobacz odpowiedź Dana Fincha: stackoverflow.com/a/521894/866172 (nie najlepiej oceniona odpowiedź, ale myślę, że to „czystsza” odpowiedź)
Jalayn
Przychodzi mi na myśl Perl i takie rzeczy jak $ _. Nienawidzę tego.
Yam Marcovic
2
Z tego, co wiem o C #, ma wiele słów kluczowych opartych na kontekście, więc nie byłoby to „sprzeczne z użyciem słów kluczowych”. Jak wskazano w odpowiedzi poniżej, prawdopodobnie po prostu chcesz użyć for () pętli.
Craige
@Jalayn Prześlij to jako odpowiedź. Jest to jedno z najlepszych podejść.
Apoorv Khurasia
@MonsterTruck: Nie sądzę, że powinien. Prawdziwym rozwiązaniem byłaby migracja tego pytania do SO i zamknięcie go jako duplikat pytania, do którego prowadzi Jalayn.
Brian

Odpowiedzi:

54

Jeśli potrzebujesz liczby pętli w foreachpętli, skorzystaj z zwykłej forpętli. foreachPętlowy przeznaczone do specyficznych zastosowań forpętli prostsze. Wygląda na to, że masz sytuację, w której prostota foreachnie jest już korzystna.

Ryathal
źródło
1
+1 - dobry punkt. Łatwo jest użyć IEnumerable.Count lub object.length z regularną pętlą for, aby uniknąć problemów z ustaleniem liczby elementów w obiekcie.
11
@ GlenH7: W zależności od implementacji IEnumerable.Countmoże być kosztowny (lub niemożliwy, jeśli jest to jednorazowe wyliczenie). Musisz wiedzieć, jakie jest podstawowe źródło, zanim będziesz mógł to zrobić bezpiecznie.
Binary Worrier
2
-1: Bill Wagner w bardziej efektywny C # opisuje dlaczego trzeba zawsze trzymać się foreachponad for (int i = 0…). Napisał kilka stron, ale mogę streścić w dwóch słowach - optymalizacja kompilatora podczas iteracji. Ok, to nie były dwa słowa.
Apoorv Khurasia
13
@MonsterTruck: cokolwiek napisał Bill Wagner, widziałem wystarczająco dużo sytuacji, takich jak ta opisana przez OP, gdzie forpętla daje lepiej czytelny kod (a teoretyczna optymalizacja kompilatora jest często przedwczesną optymalizacją).
Doc Brown,
1
@DocBrown Nie jest to tak proste jak przedwczesna optymalizacja. W przeszłości sam zidentyfikowałem wąskie gardła i naprawiłem je przy użyciu tego podejścia (ale przyznaję, że miałem do czynienia z tysiącami obiektów w kolekcji). Mam nadzieję, że wkrótce opublikuję działający przykład. Tymczasem tutaj jest Jon Skeet . [Mówi, że poprawa wydajności nie będzie znacząca, ale zależy to w dużej mierze od kolekcji i liczby obiektów].
Apoorv Khurasia
37

Możesz użyć takich anonimowych typów

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

Jest to podobne do enumeratefunkcji Pythona

for i, v in enumerate(items):
    print v, i
Kris Harper
źródło
+1 Och ... żarówka. Lubię to podejście. Dlaczego nigdy o tym nie myślałem?
Phil
17
Ktokolwiek woli to w C # niż klasyczna pętla for, ma dziwne zdanie na temat czytelności. To może być fajna sztuczka, ale nie pozwoliłbym na coś takiego w kodzie jakości produkcji. Jest znacznie głośniejszy niż klasyczny for i nie może być rozumiany jako szybki.
Falcon
4
@ Falcon, jest to ważna alternatywa, JEŚLI NIE MOŻESZ UŻYWAĆ starej mody na pętlę (która wymaga liczenia). To kilka kolekcji obiektów, nad którymi musiałem pracować ...
Fabricio Araujo,
8
@FabricioAraujo: Na pewno C # dla pętli nie wymaga zliczania. O ile wiem, są one takie same jak litery C dla pętli, co oznacza, że ​​możesz użyć dowolnej wartości boolowskiej do zakończenia pętli. Na przykład sprawdzenie końca wyliczenia, gdy MoveNext zwraca false.
Zan Lynx
1
Ma to tę dodatkową zaletę, że cały kod obsługuje przyrost na początku bloku foreach, zamiast konieczności jego znajdowania.
JeffO
13

Właśnie dodam tutaj odpowiedź Dana Fincha , zgodnie z prośbą.

Nie dawaj mi punktów, dawaj punkty Danowi Finchowi :-)

Rozwiązaniem jest napisanie następującej ogólnej metody rozszerzenia:

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

Który jest używany tak:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
Jalayn
źródło
2
To całkiem sprytne, ale myślę, że użycie normalnego dla jest bardziej „czytelne”, szczególnie jeśli na końcu ktoś, kto kończy utrzymywanie kodu, może nie być wielkim asem w .NET i niezbyt dobrze zna koncepcję metod rozszerzenia . Nadal jednak chwytam ten kawałek kodu. :)
MetalMikester
1
Mógłbym argumentować po obu stronach tego i pierwotnego pytania - zgadzam się z intencją plakatów, są przypadki, w których foreach czyta się lepiej, ale potrzebny jest indeks, ale zawsze jestem przeciwny dodawaniu więcej cukru syntaktycznego do języka, jeśli nie jest bardzo potrzebne - mam dokładnie to samo rozwiązanie we własnym kodzie o innej nazwie - strings.ApplyIndexed <T> (......)
Mark Mullin
Zgadzam się z Markiem Mullinem, możesz się kłócić po obu stronach. Myślałem, że to całkiem sprytne użycie metod rozszerzenia i pasowało do pytania, więc podzieliłem się tym.
Jalayn
5

Mogę się mylić, ale zawsze myślałem, że głównym celem pętli foreach było uproszczenie iteracji z dni STL C ++, tj.

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

porównaj to do użycia IEnumerable w .NET w foreach

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

ten drugi jest o wiele prostszy i pozwala uniknąć wielu starych pułapek. Ponadto wydają się przestrzegać prawie identycznych zasad. Na przykład nie można zmienić zawartości IEnumerable wewnątrz pętli (co ma o wiele większy sens, jeśli myślisz o tym w sposób STL). Pętla for ma jednak często inny logiczny cel niż iteracja.

Jonathan Henson
źródło
4
+1: niewiele osób pamięta, że ​​foreach jest w zasadzie cukrem syntaktycznym w stosunku do wzoru iteratora.
Steven Evers
1
Cóż, są pewne różnice; manipulacja wskaźnik gdzie obecne w .NET jest dość głęboko ukryte z programatora, ale można napisać pętli for, który wyglądał bardzo podobnie do C ++ Przykład:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS
@ KeithS, nie jeśli zdasz sobie sprawę, że wszystko, czego dotykasz .NET, który nie jest typem strukturalnym, JEST POINTEREM, tylko z inną składnią (. Zamiast ->). W rzeczywistości przekazanie typu referencyjnego do metody jest tym samym, co przekazanie go jako wskaźnika, a przekazanie go przez referencję to przekazanie go jako podwójnego wskaźnika. Ta sama rzecz. Przynajmniej tak myśli o tym osoba, której językiem ojczystym jest C ++. W pewnym sensie, uczysz się hiszpańskiego w szkole, myśląc o tym, jak język jest składniowo powiązany z twoim własnym językiem ojczystym.
Jonathan Henson
* typ wartości niestrukturalny. Przepraszam.
Jonathan Henson
2

Jeśli chodzi o kolekcję IList<T>, możesz po prostu użyć jej fordo indeksowania:

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

Jeśli nie, to możesz użyć Select(), ma przeciążenie, które daje indeks.

Jeśli to również nie jest odpowiednie, myślę, że ręczne utrzymanie tego indeksu jest dość proste, to tylko dwie bardzo krótkie linie kodu. Utworzenie słowa kluczowego specjalnie w tym celu byłoby przesadą.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
svick
źródło
+1, także, jeśli używasz indeksu dokładnie raz w pętli, możesz zrobić coś takiego foo(++i)lub w foo(i++)zależności od tego, który indeks jest potrzebny.
Job
2

Jeśli wszystko, co wiesz, to, że kolekcja jest IEnumerable, ale musisz śledzić, ile elementów przetworzyłeś do tej pory (a tym samym sumę po zakończeniu), możesz dodać kilka linii do podstawowej pętli 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
}

Możesz także dodać przyrostową zmienną indeksu do foreach:

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

Jeśli chcesz, aby wyglądał bardziej elegancko, możesz zdefiniować metodę rozszerzenia lub dwie, aby „ukryć” te szczegóły:

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()));
KeithS
źródło
1

Scoping działa również, jeśli chcesz utrzymać blok w czystości przed kolizjami z innymi nazwami ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }
Sójka
źródło
-1

Do tego używam whilepętli. Pętle forrównież działają, i wydaje się, że wiele osób je preferuje, ale lubię używać tylko forz czymś, co będzie miało określoną liczbę iteracji. IEnumerables mogą być generowane w czasie wykonywania, więc moim zdaniem whilema to większy sens. Jeśli chcesz, nazwij to nieformalną wytyczną kodowania.

Robotman
źródło