¿Hay alguna construcción de lenguaje rara que no haya encontrado (como las pocas que he aprendido recientemente, algunas en Stack Overflow) en C # para obtener un valor que represente la iteración actual de un bucle foreach?
Por ejemplo, actualmente hago algo como esto dependiendo de las circunstancias:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
foreach
es proporcionar un mecanismo de iteración común para todas las colecciones, independientemente de si son indexables (List
) o no (Dictionary
).Dictionary
no es indexable, una iteración deDictionary
lo atraviesa en un orden particular (es decir, un enumerador es indexable por el hecho de que produce elementos secuencialmente). En este sentido, podríamos decir que no estamos buscando el índice dentro de la colección, sino más bien el índice del elemento enumerado actual dentro de la enumeración (es decir, si estamos en el primer o quinto o último elemento enumerado).Respuestas:
El
foreach
es para iterar sobre colecciones que implementanIEnumerable
. Lo hace llamandoGetEnumerator
a la colección, que devolverá unEnumerator
.Este enumerador tiene un método y una propiedad:
Current
devuelve el objeto en el que se encuentra Enumerator actualmente,MoveNext
actualizaCurrent
al siguiente objeto.El concepto de índice es extraño al concepto de enumeración y no se puede hacer.
Debido a eso, la mayoría de las colecciones pueden atravesarse utilizando un indexador y la construcción del bucle for.
Prefiero utilizar un bucle for en esta situación en comparación con el seguimiento del índice con una variable local.
fuente
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
función de Python ).Ian Mercer publicó una solución similar a esta en el blog de Phil Haack :
Esto le proporciona el elemento (
item.value
) y su índice (item.i
) mediante el uso de esta sobrecarga de LINQSelect
:El
new { i, value }
está creando un nuevo objeto anónimo .Las asignaciones de montón se pueden evitar
ValueTuple
si se usa C # 7.0 o posterior:También puede eliminarlo
item.
utilizando la desestructuración automática:fuente
Finalmente, C # 7 tiene una sintaxis decente para obtener un índice dentro de un
foreach
bucle (es decir, tuplas):Se necesitaría un pequeño método de extensión:
fuente
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
para que sea más reconocible para las personas acostumbradas a otros idiomas (y quizás también cambie el orden de los parámetros de tupla). NoWithIndex
es que no sea obvio de todos modos.Podría hacer algo como esto:
fuente
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
No estoy de acuerdo con los comentarios de que un
for
bucle es una mejor opción en la mayoría de los casos.foreach
es una construcción útil, y no reemplazable por unfor
bucle en todas las circunstancias.Por ejemplo, si tiene un DataReader y recorre todos los registros usando un
foreach
, automáticamente llama a Dispose método y cierra el lector (que luego puede cerrar la conexión automáticamente). Por lo tanto, esto es más seguro ya que evita fugas de conexión incluso si olvida cerrar el lector.(Seguro, es una buena práctica cerrar siempre los lectores, pero el compilador no lo detectará si no lo hace; no puede garantizar que haya cerrado todos los lectores, pero puede hacer que sea más probable que no pierda conexiones al obtener en el hábito de usar foreach.)
Puede haber otros ejemplos de que la llamada implícita del
Dispose
método sea útil.fuente
foreach
es diferentefor
(y más cercanowhile
) en Programmers.SE .Respuesta literal: advertencia, el rendimiento puede no ser tan bueno como simplemente usar un
int
para rastrear el índice. Al menos es mejor que usarIndexOf
.Solo necesita usar la sobrecarga de indexación de Select para ajustar cada elemento de la colección con un objeto anónimo que conozca el índice. Esto se puede hacer contra cualquier cosa que implemente IEnumerable.
fuente
Con LINQ, C # 7 y el
System.ValueTuple
paquete NuGet, puede hacer esto:Puede usar la
foreach
construcción regular y poder acceder al valor y al índice directamente, no como miembro de un objeto, y mantiene ambos campos solo en el alcance del bucle. Por estas razones, creo que esta es la mejor solución si puede usar C # 7 ySystem.ValueTuple
.fuente
Usando la respuesta de @ FlySwat, se me ocurrió esta solución:
Obtiene el enumerador usando
GetEnumerator
y luego realiza un bucle con unfor
bucle. Sin embargo, el truco es hacer que la condición del buclelistEnumerator.MoveNext() == true
.Dado que el
MoveNext
método de un enumerador devuelve verdadero si hay un siguiente elemento y se puede acceder a él, lo que hace que la condición del bucle detenga el bucle cuando nos quedemos sin elementos para iterar.fuente
IDisposable
.No hay nada de malo en usar una variable de contador. De hecho, si se utiliza
for
,foreach
while
odo
, una variable de contador debe declararse e incrementarse en algún lugar.Así que usa este modismo si no estás seguro si tienes una colección indexada adecuadamente:
De lo contrario, use este si sabe que su colección indexable es O (1) para el acceso al índice (que será para
Array
y probablemente paraList<T>
(la documentación no dice), pero no necesariamente para otros tipos (comoLinkedList
)):Nunca debería ser necesario operarlo 'manualmente'
IEnumerator
invocandoMoveNext()
e interrogandoCurrent
: leforeach
está ahorrando esa molestia particular ... si necesita omitir elementos, simplemente use uncontinue
en el cuerpo del bucle.Y solo para completar, dependiendo de lo que estaba haciendo con su índice (las construcciones anteriores ofrecen mucha flexibilidad), puede usar Parallel LINQ:
Usamos lo
AsParallel()
anterior, porque ya es 2014, y queremos hacer un buen uso de esos múltiples núcleos para acelerar las cosas. Además, para LINQ 'secuencial', solo obtienes unForEach()
método de extensiónList<T>
yArray
... y no está claro que usarlo sea mejor que hacer un simpleforeach
, ya que todavía estás ejecutando un solo subproceso para una sintaxis más fea.fuente
Puede ajustar el enumerador original con otro que contenga la información del índice.
Aquí está el código para la
ForEachHelper
clase.fuente
Aquí hay una solución que se me ocurrió para este problema
Código original:
Código actualizado
Método de extensión:
fuente
Simplemente agregue su propio índice. Mantenlo simple.
fuente
Solo funcionará para una Lista y no para IEnumerable, pero en LINQ hay esto:
@ Jonathan No dije que era una gran respuesta, solo dije que solo estaba demostrando que era posible hacer lo que me pidió :)
@Graphain No esperaría que fuera rápido: no estoy completamente seguro de cómo funciona, podría reiterar a través de toda la lista cada vez que encuentre un objeto que coincida, lo que sería una gran cantidad de comparaciones.
Dicho esto, List podría mantener un índice de cada objeto junto con el recuento.
¿Jonathan parece tener una mejor idea, si quisiera dar más detalles?
Sin embargo, sería mejor llevar un recuento de lo que estás haciendo en el foreach, más simple y más adaptable.
fuente
C # 7 finalmente nos da una forma elegante de hacer esto:
fuente
¿Por qué foreach?
La forma más simple es usar for en lugar de foreach si está usando List :
O si quieres usar foreach:
Puede usar esto para conocer el índice de cada ciclo:
fuente
Esto funcionaría para colecciones de apoyo
IList
.fuente
O(n^2)
que en la mayoría de las implementacionesIndexOf
esO(n)
. 2) Esto falla si hay elementos duplicados en la lista.Así es como lo hago, lo cual es bueno por su simplicidad / brevedad, pero si estás haciendo mucho en el cuerpo del bucle
obj.Value
, envejecerá bastante rápido.fuente
Esta respuesta: presionar al equipo de lenguaje C # para obtener soporte directo de idioma.
La respuesta principal dice:
Si bien esto es cierto para la versión actual del lenguaje C # (2020), este no es un límite conceptual de CLR / Lenguaje, se puede hacer.
El equipo de desarrollo del lenguaje C # de Microsoft podría crear una nueva característica del lenguaje C #, al agregar soporte para una nueva interfaz IIndexedEnumerable
Si
foreach ()
se usa ywith var index
está presente, el compilador espera que la colección de elementos declare laIIndexedEnumerable
interfaz. Si la interfaz está ausente, el compilador puede rellenar la fuente con un objeto IndexedEnumerable, que agrega el código para rastrear el índice.Más tarde, el CLR puede actualizarse para tener un seguimiento de índice interno, que solo se usa si
with
se especifica la palabra clave y la fuente no se implementa directamenteIIndexedEnumerable
Por qué:
Si bien la mayoría de las personas aquí no son empleados de Microsoft, esta es una respuesta correcta, puede presionar a Microsoft para que agregue dicha función. Ya podría construir su propio iterador con una función de extensión y usar tuplas , pero Microsoft podría rociar el azúcar sintáctico para evitar la función de extensión
fuente
Si la colección es una lista, puede usar List.IndexOf, como en:
fuente
Es mejor usar
continue
una construcción segura de palabras clave como estafuente
Puedes escribir tu bucle así:
Después de agregar la siguiente estructura y método de extensión.
La estructura y el método de extensión encapsulan la funcionalidad Enumerable.Select.
fuente
Mi solución para este problema es un método de extensión
WithIndex()
,http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
Úselo como
fuente
struct
para el par (índice, elemento).Por interés, Phil Haack acaba de escribir un ejemplo de esto en el contexto de un delegado de Razor Templated ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )
Efectivamente, escribe un método de extensión que envuelve la iteración en una clase "IteratedItem" (ver más abajo) permitiendo el acceso al índice y al elemento durante la iteración.
Sin embargo, si bien esto estaría bien en un entorno que no sea Razor si está haciendo una sola operación (es decir, una que podría proporcionarse como una lambda), no será un reemplazo sólido de la sintaxis de for / foreach en contextos que no sean Razor .
fuente
No creo que esto deba ser bastante eficiente, pero funciona:
fuente
Construí esto en LINQPad :
También puedes usar
string.join
:fuente
n
elementos, entonces las operaciones sonn * n
on
cuadráticas. en.wikipedia.org/wiki/…No creo que haya una manera de obtener el valor de la iteración actual de un bucle foreach. Contarte a ti mismo, parece ser la mejor manera.
¿Puedo preguntar por qué quieres saber?
Parece que lo más probable es que estés haciendo una de tres cosas:
1) Obtener el objeto de la colección, pero en este caso ya lo tiene.
2) Contando los objetos para su posterior procesamiento ... las colecciones tienen una propiedad Count que puede utilizar.
3) Establecer una propiedad en el objeto en función de su orden en el bucle ... aunque podría configurarlo fácilmente al agregar el objeto a la colección.
fuente
A menos que su colección pueda devolver el índice del objeto mediante algún método, la única forma es usar un contador como en su ejemplo.
Sin embargo, cuando se trabaja con índices, la única respuesta razonable al problema es usar un bucle for. Cualquier otra cosa introduce la complejidad del código, sin mencionar la complejidad del tiempo y el espacio.
fuente
¿Qué tal algo como esto? Tenga en cuenta que myDelimitedString puede ser nulo si myEnumerable está vacío.
fuente
Acabo de tener este problema, pero pensar en el problema en mi caso dio la mejor solución, sin relación con la solución esperada.
Podría ser un caso bastante común, básicamente, estoy leyendo de una lista de origen y creando objetos basados en ellos en una lista de destino, sin embargo, tengo que verificar si los elementos de origen son válidos primero y quiero devolver la fila de cualquier error. A primera vista, quiero obtener el índice en el enumerador del objeto en la propiedad Current, sin embargo, como estoy copiando estos elementos, implícitamente conozco el índice actual de todos modos desde el destino actual. Obviamente, depende de su objeto de destino, pero para mí fue una Lista, y lo más probable es que implemente ICollection.
es decir
No siempre es aplicable, pero a menudo creo que vale la pena mencionarlo.
De todos modos, el punto es que a veces ya hay una solución no obvia en la lógica que tienes ...
fuente
No estaba seguro de lo que estaba tratando de hacer con la información del índice basada en la pregunta. Sin embargo, en C #, generalmente puede adaptar el método IEnumerable.Select para obtener el índice de lo que desee. Por ejemplo, podría usar algo como esto para saber si un valor es impar o par.
Esto le daría un diccionario por nombre de si el elemento era impar (1) o par (0) en la lista.
fuente