c# - una - ¿Cómo se obtiene el índice de la iteración actual de un bucle foreach?
obtener valores de una lista c# (30)
¿Qué tal algo como esto? Tenga en cuenta que myDelimitedString puede ser nulo si myEnumerable está vacío.
interface IIndexedEnumerable<T> : IEnumerable<T>
{
//Not index, because sometimes source IEnumerables are transient
public long IterationNumber { get; }
}
¿Hay algún constructo de lenguaje raro que no haya encontrado (como los pocos que he aprendido recientemente, algunos sobre Desbordamiento de pila) en C # para obtener un valor que representa 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++;
}
A menos que su colección pueda devolver el índice del objeto a través de 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.
Acabo de tener este problema, pero pensar en el problema en mi caso dio la mejor solución, no relacionada 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 comprobar si los elementos de origen son válidos primero y quiero devolver la fila de cualquiera error. A primera vista, quiero obtener el índice en el enumerador del objeto en la propiedad Actual, sin embargo, como estoy copiando estos elementos, de todos modos, conozco implícitamente el índice actual del destino actual. Obviamente, depende de su objeto de destino, pero para mí era una Lista, y muy probablemente implementará ICollection.
es decir
int i = 0;
foreach (var item in Collection)
{
item.index = i;
++i;
}
No siempre es aplicable, pero a menudo lo suficiente como para que valga la pena mencionarlo, creo.
De todos modos, el punto es que a veces hay una solución no obvia en la lógica que tienes ...
Aquí hay una solución que se me ocurrió para este problema
Código original:
int index=0;
foreach (var item in enumerable)
{
blah(item, index); // some code that depends on the index
index++;
}
Código actualizado
enumerable.ForEach((item, index) => blah(item, index));
Método de extensión:
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;
}
Así es como lo hago, lo cual es bueno por su simplicidad / brevedad, pero si estás haciendo mucho en el valor del cuerpo del bucle obj.Value
, va a envejecer bastante rápido.
foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
...
}
C # 7 finalmente nos da una manera elegante de hacer esto:
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}");
}
}
}
Construí esto en LINQPad :
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));
También string.join
usar string.join
:
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;
}
Finalmente, C # 7 tiene una sintaxis decente para obtener un índice dentro de un bucle foreach
(es decir, tuplas):
foreach (var (item, index) in collection.WithIndex())
{
Debug.WriteLine($"{index}: {item}");
}
Se necesitaría un pequeño método de extensión:
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
=> self.Select((item, index) => (item, index));
Ian Mercer publicó una solución similar a esta en el blog de Phil Haack :
foreach (var item in Model.Select((value, i) => new { i, value }))
{
var value = item.value;
var index = item.i;
}
Esto le permite obtener el artículo ( item.value
) y su índice ( item.i
) usando esta sobrecarga de Linq''s Select
:
el segundo parámetro de la función [dentro de Selección] representa el índice del elemento fuente.
El new { i, value }
está creando un nuevo objeto anónimo .
La respuesta principal dice:
"Obviamente, el concepto de índice es ajeno al concepto de enumeración y no se puede hacer".
Si bien esto es cierto para la versión actual de C #, esto no es un límite conceptual.
La creación de una nueva función de lenguaje C # por parte de MS podría resolver esto, junto con el soporte para una nueva interfaz IIndexedEnumerable
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
Si foreach se pasa un IEnumerable y no puede resolver un IIndexedEnumerable, pero se le pregunta con el índice var, entonces el compilador de C # puede envolver la fuente con un objeto IndexedEnumerable, que agrega el código para el seguimiento del índice.
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);
}
}
Por qué:
- Foreach se ve mejor, y en aplicaciones de negocios rara vez es un cuello de botella de rendimiento
- Foreach puede ser más eficiente en la memoria. Tener un conjunto de funciones en lugar de convertir a nuevas colecciones en cada paso. A quién le importa si utiliza unos cuantos ciclos de CPU más, si hay menos fallos de caché de CPU y menos GC.Collects
- Requerir que el codificador agregue un código de seguimiento de índice, estropea la belleza
- Es bastante fácil de implementar (gracias a MS) y es compatible con versiones anteriores
Si bien la mayoría de las personas aquí no son MS, esta es una respuesta correcta, y puede presionar a MS para que agregue dicha característica. Ya podría crear su propio iterador con una función de extensión y usar tuplas , pero MS podría rociar el azúcar sintáctico para evitar la función de extensión
Mejor usar palabras clave continue
la construcción segura como esta
int i=-1;
foreach (Object o in collection)
{
++i;
//...
continue; //<--- safe to call, index will be increased
//...
}
Mi solución para este problema es un método de extensión WithIndex()
,
Utilízalo como
var joinResult = string.Join(",", listOfNames);
No creo que esto deba ser bastante eficiente, pero funciona:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}
No creo que haya una manera de obtener el valor de la iteración actual de un bucle foreach. Contándote, parece ser la mejor manera.
¿Puedo preguntar, por qué quieres saber?
Parece que probablemente estarías haciendo una de tres cosas:
1) Obteniendo el objeto de la colección, pero en este caso ya lo tienes.
2) Contando los objetos para su posterior procesamiento posterior ... 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.
No estoy de acuerdo con los comentarios de que un bucle for
es una mejor opción en la mayoría de los casos.
foreach
es una construcción útil, y no reemplazable por un bucle for
en todas las circunstancias.
Por ejemplo, si tiene un DataReader y recorre todos los registros utilizando un foreach
, automáticamente llama al método Dispose y cierra el lector (que luego puede cerrar la conexión automáticamente). Por lo tanto, esto es más seguro ya que evita las fugas de conexión incluso si olvida cerrar el lector.
(Claro que 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 las conexiones obteniendo en el hábito de usar foreach.)
Puede haber otros ejemplos de la llamada implícita del método Dispose
que sea útil.
No hay nada de malo en usar una variable de contador. De hecho, ya sea que use, foreach
while
o do
, una variable de contador debe ser declarada e incrementada en alguna parte.
Entonces, use este idioma si no está seguro de tener una colección adecuadamente indexada:
var i = 0;
foreach (var e in collection) {
// Do stuff with ''e'' and ''i''
i++;
}
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 para la List<T>
(la documentación no lo dice), pero no necesariamente para otros tipos (como como 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''
}
Nunca debería ser necesario operar manualmente el IEnumerator
invocando MoveNext()
e interrogando a Current
- foreach
está ahorrando esa molestia en particular ... si necesita omitir elementos, solo use continue
en el cuerpo del bucle.
Y solo para estar completo, dependiendo de lo que estabas haciendo con tu índice (las construcciones anteriores ofrecen mucha flexibilidad), puedes usar 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));
Usamos AsParallel()
arriba, 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 un método de extensión ForEach()
en List<T>
y Array
... y no está claro que usarlo sea mejor que hacer un simple foreach
, ya que todavía estás ejecutando single- roscado para la sintaxis más fea.
Para interesar, Phil Haack acaba de escribir un ejemplo de esto en el contexto de un delegado con plantilla de Razor ( 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 de "IteratedItem" (ver más abajo) que permite el acceso al índice y al elemento durante la iteración.
foreach (Object o in collection)
{
// ...
@collection.IndexOf(o)
}
Sin embargo, aunque esto estaría bien en un entorno que no sea de Razor si está realizando una sola operación (es decir, una que podría proporcionarse como un lambda) no va a ser un reemplazo sólido de la sintaxis for / foreach en contextos que no sean Razor .
Podría ajustar el enumerador original con otro que contenga la información del índice.
foreach (var item in ForEachHelper.WithIndex(collection))
{
Console.Write("Index=" + item.Index);
Console.Write(";Value= " + item.Value);
Console.Write(";IsLast=" + item.IsLast);
Console.WriteLine();
}
Aquí está el código para la clase 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;
}
}
}
Podría hacer algo como esto:
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));
}
}
Puedes escribir tu bucle así:
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; }
}
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.
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);
Respuesta literal: advertencia, el rendimiento puede no ser tan bueno como usar un int
para rastrear el índice. Al menos es mejor que usar IndexOf
.
Solo necesita usar la sobrecarga de indexación de Select para envolver 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.
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);
}
Si la colección es una lista, puede usar List.IndexOf, como en:
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.
Solo agrega tu propio índice. Mantenlo simple.
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);
}
Solo va a funcionar para una lista y no para cualquier IEnumerable, pero en LINQ hay esto:
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 no dije que era una gran respuesta, solo dije que solo demostraba que era posible hacer lo que él pedía :)
@Graphain No esperaría que fuera rápido. No estoy del todo seguro de cómo funciona, podría reiterar a través de toda la lista cada vez que encuentre un objeto coincidente, lo que sería un infierno de comparación.
Dicho esto, List podría mantener un índice de cada objeto junto con el conteo.
Jonathan parece tener una mejor idea, si se explicara?
Sin embargo, sería mejor mantener un recuento de lo que estás haciendo en el foreach, más simple y más adaptable.
Usando LINQ, C # 7 y el paquete System.ValueTuple
NuGet, puede hacer esto:
foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
Console.WriteLine(value + " is at index " + index);
}
Puede usar la construcción regular de foreach
y poder acceder al valor y al índice directamente, no como miembro de un objeto, y mantiene ambos campos solo en el ámbito del bucle. Por estas razones, creo que esta es la mejor solución si puede usar C # 7 y System.ValueTuple
.
Usando la respuesta de @ FlySwat, se me ocurrió esta solución:
//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
}
Obtiene el enumerador utilizando GetEnumerator
y luego realiza un bucle utilizando un bucle for
. Sin embargo, el truco es hacer que la condición listEnumerator.MoveNext() == true
del bucle sea listEnumerator.MoveNext() == true
.
Dado que el método MoveNext
de un enumerador devuelve verdadero si hay un elemento siguiente y se puede acceder a él, la condición de bucle hace que el bucle se detenga cuando nos quedemos sin elementos para iterar.
foreach
es para iterar sobre colecciones que implementan IEnumerable
. Lo hace llamando a GetEnumerator
en la colección, que devolverá un Enumerator
.
Este enumerador tiene un método y una propiedad:
- MoveNext ()
- Corriente
Current
devuelve el objeto en el que se encuentra actualmente el MoveNext
, MoveNext
actualiza Current
al siguiente objeto.
Obviamente, el concepto de índice es ajeno al concepto de enumeración y no se puede hacer.
Debido a eso, la mayoría de las colecciones se pueden recorrer utilizando un indexador y la construcción de bucle for.
Prefiero usar un bucle for en esta situación en comparación con el seguimiento del índice con una variable local.
¿Por qué foreach?
La forma más sencilla es usar for en lugar de foreach si está usando List .
for(int i = 0 ; i < myList.Count ; i++)
{
// Do Something...
}
O si quieres usar foreach:
foreach (string m in myList)
{
// Do something...
}
Puedes usar esto para calcular el índice de cada Loop:
myList.indexOf(m)
No estaba seguro de lo que intentaba 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 determinar si un valor es par o impar.
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]});
}
}
Esto le daría un diccionario por nombre de si el elemento era impar (1) o incluso (0) en la lista.
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
Esto funcionaría para las colecciones que apoyan a IList
.