c# - with - uso array php




Come si ottiene l'indice dell'iterazione corrente di un ciclo foreach? (20)

C'è qualche raro costrutto linguistico che non ho incontrato (come i pochi che ho imparato di recente, alcuni su Stack Overflow) in C # per ottenere un valore che rappresenta l'iterazione corrente di un ciclo foreach?

Ad esempio, attualmente faccio qualcosa di simile a seconda delle circostanze:

int i=0;
foreach (Object o in collection)
{
    // ...
    i++;
}

È possibile avvolgere l'enumeratore originale con un altro che contiene le informazioni sull'indice.

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

Ecco il codice per la classe 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;
        }            
    }
}

A meno che la tua raccolta non possa restituire l'indice dell'oggetto tramite un metodo, l'unico modo è usare un contatore come nel tuo esempio.

Tuttavia, quando si lavora con gli indici, l'unica risposta ragionevole al problema è l'uso di un ciclo for. Qualsiasi altra cosa introduce la complessità del codice, per non parlare della complessità del tempo e dello spazio.


C # 7 ci offre finalmente un modo elegante per farlo:

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

Che ne dici di questo? Si noti che myDelimitedString potrebbe essere nullo se myEnumerable è vuoto.

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

Funzionerà solo per un elenco e non per qualsiasi oggetto IEnumerable, ma in LINQ è presente questo:

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 Non ho detto che è stata una grande risposta, ho appena detto che stava solo mostrando che era possibile fare ciò che chiedeva :)

@Graphain Non mi aspetterei che fosse veloce - non sono del tutto sicuro di come funzioni, potrebbe reiterare l'intera lista ogni volta per trovare un oggetto corrispondente, che sarebbe un helluvalot di confronto.

Detto questo, l'elenco potrebbe mantenere un indice di ogni oggetto insieme al conteggio.

Jonathan sembra avere un'idea migliore, se volesse elaborare?

Sarebbe meglio tenere conto di dove sei nel foreach, più semplice e più adattabile.


Ho appena avuto questo problema, ma pensare al problema nel mio caso ha fornito la soluzione migliore, non correlata alla soluzione attesa.

Potrebbe essere un caso abbastanza comune, in pratica sto leggendo da un elenco di fonti e creando oggetti basati su di essi in un elenco di destinazione, tuttavia, devo verificare se gli elementi di origine sono validi per primi e voglio restituire la riga di qualsiasi errore. A prima vista, voglio ottenere l'indice nell'enumeratore dell'oggetto nella proprietà Current, tuttavia, poiché sto copiando questi elementi, conosco implicitamente l'indice corrente comunque dalla destinazione corrente. Ovviamente dipende dall'oggetto di destinazione, ma per me era un elenco e molto probabilmente implementerà ICollection.

vale a dire

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

Non sempre applicabile, ma abbastanza spesso per essere degno di nota, penso.

Ad ogni modo, il punto è che a volte c'è una soluzione non ovvia già nella logica che hai ...


Il foreach è per iterare su raccolte che implementano IEnumerable . Lo fa chiamando GetEnumerator sulla raccolta, che restituirà un Enumerator .

Questo Enumeratore ha un metodo e una proprietà:

  • MoveNext ()
  • attuale

Current restituisce l'oggetto che Enumerator è attualmente attivo, MoveNext aggiorna Current all'oggetto successivo.

Ovviamente, il concetto di un indice è estraneo al concetto di enumerazione e non può essere fatto.

Per questo motivo, la maggior parte delle collezioni può essere attraversata utilizzando un indicizzatore e il costrutto for loop.

Preferisco di gran lunga utilizzare un ciclo for in questa situazione rispetto al tracciamento dell'indice con una variabile locale.


Infine C # 7 ha una sintassi decente per ottenere un indice all'interno di un ciclo foreach (cioè tuple):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Sarebbe necessario un piccolo metodo di estensione:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 


La risposta principale afferma:

"Ovviamente, il concetto di un indice è estraneo al concetto di enumerazione e non può essere fatto."

Anche se questo è vero per la versione C # corrente, questo non è un limite concettuale.

La creazione di una nuova funzionalità in linguaggio C # dalla MS potrebbe risolvere questo problema, insieme al supporto per una nuova interfaccia IIndexedEnumerable

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

Se foreach viene passato a un oggetto IEnumerable e non può risolvere un oggetto IIndexedEnumerable, ma viene richiesto con l'indice var, il compilatore C # può racchiudere l'origine con un oggetto IndexedEnumerable, che aggiunge nel codice per tracciare l'indice.

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

Perché:

  • Foreach sembra più bello, e nelle applicazioni aziendali è raramente un collo di bottiglia delle prestazioni
  • Foreach può essere più efficiente sulla memoria. Avere una pipeline di funzioni invece di convertirle in nuove raccolte ad ogni passaggio. A chi importa se utilizza qualche altro ciclo della CPU, se ci sono meno errori di cache della CPU e meno GC.Collects
  • Richiedere al codificatore di aggiungere il codice di tracciamento dell'indice, rovina la bellezza
  • È abbastanza facile da implementare (grazie MS) ed è retrocompatibile

Mentre la maggior parte delle persone qui non sono MS, questa è una risposta corretta, e puoi fare pressione sulla SM per aggiungere una funzione del genere. Potresti già costruire il tuo iteratore con una funzione di estensione e usare tuple , ma MS potrebbe cospargere lo zucchero sintattico per evitare la funzione di estensione


Non c'è niente di sbagliato nell'usare una variabile contatore. In effetti, indipendentemente dal fatto che si usi foreach while o do , una variabile contatore deve essere dichiarata e incrementata.

Quindi usa questo idioma se non sei sicuro di avere una collezione indicizzata in modo appropriato:

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

Altrimenti usalo se sai che la tua collezione indicizzabile è O (1) per l'accesso all'indice (che sarà per Array e probabilmente per List<T> (la documentazione non dice), ma non necessariamente per altri tipi (come come 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'
}

Non dovrebbe mai essere necessario "manualmente" far funzionare l' IEnumerator invocando MoveNext() e interrogando Current - foreach sta risparmiando quel particolare fastidio ... se hai bisogno di saltare gli elementi, usa solo un continue nel corpo del loop.

E solo per completezza, a seconda di cosa stavi facendo con il tuo indice (i costrutti di cui sopra offrono molta flessibilità), potresti usare 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));

Usiamo AsParallel() sopra, perché è già il 2014, e vogliamo fare buon uso di quei core multipli per accelerare le cose. Inoltre, per LINQ 'sequenziale', si ottiene solo un metodo di estensione ForEach() su List<T> e Array ... e non è chiaro che utilizzarlo sia meglio che fare un foreach semplice, dato che si sta ancora eseguendo single- threaded per la più brutta sintassi.


Non credo che ci sia un modo per ottenere il valore dell'attuale iterazione di un ciclo foreach. Contare te stesso, sembra essere il modo migliore.

Posso chiederlo, perché vorresti saperlo?

Sembra che tu stia molto probabilmente facendo una delle tre cose:

1) Ottenere l'oggetto dalla raccolta, ma in questo caso lo hai già.

2) Contando gli oggetti per l'elaborazione post successiva ... le raccolte hanno una proprietà Count che potresti utilizzare.

3) Impostazione di una proprietà sull'oggetto in base al suo ordine nel ciclo ... anche se potresti facilmente impostarla quando hai aggiunto l'oggetto alla raccolta.


Non sono d'accordo con i commenti sul fatto che un ciclo for è una scelta migliore nella maggior parte dei casi.

foreach è un costrutto utile e non sostituibile da un ciclo for in tutte le circostanze.

Ad esempio, se si dispone di un DataReader e si esegue il ciclo di tutti i record utilizzando un foreach esso chiama automaticamente il metodo Dispose e chiude il lettore (che può quindi chiudere automaticamente la connessione). Questo è quindi più sicuro in quanto impedisce perdite di connessione anche se si dimentica di chiudere il lettore.

(Certo è una buona abitudine chiudere sempre i lettori ma il compilatore non lo prenderà se non lo fai - non puoi garantire di aver chiuso tutti i lettori ma puoi farlo più probabilmente non perderai connessioni ottenendo l'abitudine di usare foreach.)

Potrebbero esserci altri esempi del richiamo implicito del metodo Dispose che è utile.


Per interesse, Phil Haack ha appena scritto un esempio di questo nel contesto di un Razor Templated Delegate ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Scrive efficacemente un metodo di estensione che avvolge l'iterazione in una classe "IteratedItem" (vedi sotto) che consente l'accesso all'indice e all'elemento durante l'iterazione.

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

Tuttavia, mentre questo andrebbe bene in un ambiente non Razor se si sta facendo una singola operazione (cioè una che potrebbe essere fornita come una lambda) non sarà una sostituzione solida della sintassi for / foreach in contesti non Razor .


Puoi scrivere il tuo loop in questo modo:

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

Dopo aver aggiunto il seguente metodo struct ed extension.

Il metodo struct e extension incapsula la funzionalità 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);

Questo è il modo in cui lo faccio, che è bello per la sua semplicità / brevità, ma se stai facendo molto nel corpo del ciclo obj.Value , obj.Value piuttosto veloce.

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

Se la raccolta è una lista, puoi utilizzare List.IndexOf, come in:

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.

Usando LINQ, C # 7 e il pacchetto System.ValueTuple NuGet, puoi fare ciò:

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

È possibile utilizzare il normale costrutto foreach ed essere in grado di accedere al valore e all'indice direttamente, non come membro di un oggetto, e mantiene entrambi i campi solo nell'ambito del ciclo. Per questi motivi, credo che questa sia la soluzione migliore se si è in grado di utilizzare C # 7 e System.ValueTuple .


Perché insistere ?!

Il modo più semplice è usare for invece di foreach se stai usando List .

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

O se vuoi usare foreach:

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

puoi usare questo per khow index di ogni Loop:

myList.indexOf(m)

Non ero sicuro di cosa stavi cercando di fare con le informazioni dell'indice basate sulla domanda. Tuttavia, in C #, in genere è possibile adattare il metodo IEnumerable.Select per ottenere l'indice da ciò che si desidera. Per esempio, potrei usare qualcosa del genere per determinare se un valore è pari o dispari.

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

Questo ti darebbe un dizionario per nome se l'elemento era dispari (1) o pari (0) nell'elenco.







foreach