IEnumerable richiede l'uso di foreach nelle raccolte?

2

Ho la seguente classe che non implementa IEnumerable ma funziona perfettamente con foreach . Inoltre, gli array funzionano senza implementare IEnumerable .

Quindi, continua a dire che IEnumerable deve essere implementato nelle raccolte da utilizzare con foreach . Sono confuso, per favore aiutami.

class Parent
    {
        public int MyProperty { get; set; }
        public void GetData()
        {
            Console.WriteLine("parent");
        }
    }



Parent[] p = new Parent[3]
    {
        new Parent(){MyProperty=90},
        new Parent(){MyProperty=50},
        new Parent(){MyProperty=100}
    };


foreach (var item in p)
    {
        Parent it = item;
        Console.WriteLine(it.MyProperty);
    }
    
posta Roshan Fernando 25.06.2016 - 19:01
fonte

3 risposte

8

La documentazione ufficiale mi sembra semplice:

The foreach statement repeats a group of embedded statements for each element in an array or an object collection that implements the System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> interface.

Si noti che la risposta di kai , supportata dalla specifica del linguaggio C #, mostra che la documentazione è troppo restrittiva: non è necessario implementare una di queste interfacce per utilizzare una classe con foreach . Questi sono dettagli di implementazione che sono interessanti da soli, ma non pertinenti per la tua domanda originale, dal momento che nel tuo codice di esempio, stai effettivamente utilizzando un IEnumerable .

Quello che succede è che la sequenza che usi è una matrice, e secondo la documentazione , sottolineatura mia:

Array types are reference types derived from the abstract base type Array. Since this type implements IEnumerable and IEnumerable<T>, you can use foreach iteration on all arrays in C#.

Allo stesso modo, puoi consultare la definizione di Array classe :

public abstract class Array : ICloneable, IList, ICollection, 
    IEnumerable, IStructuralComparable, IStructuralEquatable

Vedi IEnumerable tra le interfacce?

Più interessante, esplorando il risultato di typeof(int[]) ti fornisce questo elenco per la proprietà ImplementedInterfaces :

  • typeof(ICloneable)
  • typeof(IList)
  • typeof(ICollection)
  • typeof(IEnumerable)
  • typeof(IStructuralComparable)
  • typeof(IStructuralEquatable)
  • typeof(IList<Int32>)
  • typeof(ICollection<Int32>)
  • typeof(IEnumerable<Int32>)
  • typeof(IReadOnlyList<Int32>)
  • typeof(IReadOnlyCollection<Int32>)

dove puoi vedere anche le interfacce generiche aggiuntive. (Questo spiega anche perché puoi scrivere IList<int> a = new[] { 1, 2, 3 }; .)

Allo stesso modo, List<T> , ConcurrentQueue<T> , ecc. implementa IEnumerable e Enumerable<T> .

Quindi quando lo dici:

And also , arrays are working without implementing IEnumerable.

questo semplicemente non è vero. Le matrici sono che implementano IEnumerable . Per quanto riguarda:

I have following class that didnt implement IEnumerable but working perfectly with foreach.

sarebbe interessante vedere il codice reale (quello che hai nella tua domanda usa un array). Quello che probabilmente sta succedendo è che la tua classe, senza dichiarare esplicitamente IEnumerable o IEnumerable<T> tra le sue interfacce, implementa un'interfaccia più specifica, come ICollection<T> , che, a sua volta, eredita da IEnumerable :

public interface ICollection<T> : IEnumerable<T>, IEnumerable

E se no, beh, probabilmente sei nel caso illustrato nella risposta di kai.

    
risposta data 25.06.2016 - 19:11
fonte
6

Chiarimento modificato: mentre l'implementazione dell'interfaccia non è richiesta , è in realtà implementata nel tuo esempio, dal momento che stai iterando su un array, e gli array implementano IEnumerable in C #.

Non è richiesto. La parola chiave foreach fondamentalmente solo "riscrive" il tuo codice in modo che diventi un ciclo while chiamando MoveNext() e guardando Current , ma l'interfaccia IEnumerable non è necessaria. Questo è supportato dalla Specifica lingua C # nella sezione 15.8.4 The foreach statement

Prova a compilare il seguente codice e vedrai che msbuild non ha nulla da dire al riguardo:

public class Foo<T>
{
    public Foo(T value)
    {
        Value = value;
    }

    public T Value { get; }

    public FooEnumerator<T> GetEnumerator()
    {
        return new FooEnumerator<T>(Value);
    }
}

public class FooEnumerator<T>
{
    private bool _hasValue = true;
    private readonly T _value;

    public FooEnumerator(T value)
    {
        _value = value;
    }

    public bool MoveNext()
    {
        if (_hasValue)
        {
            Current = _value;
            return true;
        }
        else
        {
            _hasValue = false;
            return _hasValue;
        }
    }

    public T Current { get; private set; }
}

public class Test
{
    public static void Run()
    {
        foreach (var x in new Foo<int>(1))
        {
            Console.WriteLine(x);
        }
    }
}

Tutto ciò che è richiesto per utilizzare la parola chiave foreach è che esiste un metodo chiamato GetEnumerator() che restituisce QUALSIASI tipo che ha un metodo chiamato MoveNext() che restituisce un bool e una proprietà chiamata Current (di qualsiasi tipo) con almeno un getter visibile

Funziona in modo simile alla comprensione delle query LINQ. Il seguente codice viene compilato ed eseguito senza problemi:

public class Foo<T>
{
    public Foo(T value)
    {
        Value = value;
    }

    public T Value { get; }

    public Foo<U> Select<U>(Func<T, U> f)
    {
        return new Foo<U>(f(Value));
    }

    public Foo<U> SelectMany<U>(Func<T, Foo<U>> f)
    {
        return f(Value);
    }

    public Foo<V> SelectMany<U, V>(Func<T, Foo<U>> f, Func<T, U, V> g)
    {
        return new Foo<V>(g(Value, f(Value).Value));
    }
}

public class Test
{
    public static void Run()
    {
        var result =
            from x in new Foo<int>(5)
            from y in new Foo<int>(9)
            select (x + y).ToString();

        // result is now a Foo<string> containing the value "14"
    }
}
    
risposta data 25.06.2016 - 19:12
fonte
5

Eric Lippert in realtà ha un pezzo su questo:

link

La spiegazione di Eric:

What is required is that the type of the collection must have a public method called GetEnumerator, and that must return some type that has a public property getter called Current and a public method MoveNext that returns a bool.

Interessante (e come l'articolo continua a menzionare) questa è una reliquia della mancanza di generici del C # 1.0. Il compilatore C # ha utilizzato la tipizzazione di Duck per consentire agli Enumeratori di restituire il tipo appropriato per la raccolta invece di restituire Object e di incorrere in costi di boxing / unboxing per i tipi di valore.

Ricordo di averne appreso quando ero ancora all'università e ho imparato C #. Mi ha fatto sentire icky in quel momento. Ora lo trovo un po 'intelligente!

    
risposta data 25.06.2016 - 19:22
fonte

Leggi altre domande sui tag