web-dev-qa-db-ja.com

コレクションでforeachを使用するには、IEnumerableの実装が必要ですか?

IEnumerableを実装していない次のクラスがありますが、foreachで完全に動作しています。また、配列はIEnumerableを実装しなくても機能します。

では、なぜIEnumerableforeachで使用するには、コレクションに実装する必要があると言っているのですか。混乱しています。助けてください。

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);
    }
4
Roshan Fernando

公式ドキュメント は私には簡単に見えます:

foreachステートメントは、_System.Collections.IEnumerable_または_System.Collections.Generic.IEnumerable<T>_インターフェイスを実装する配列またはオブジェクトコレクションの各要素に対して、埋め込みステートメントのグループを繰り返します。

C-#言語仕様に裏打ちされた kai's answer は、ドキュメントの制限が厳しすぎることを示しています。foreachを持つクラスを使用するために、これらのインターフェイスの1つを実装する必要はありません。 。これらはそれ自体興味深い実装の詳細ですが、サンプルコードでは実際にIEnumerableを使用しているため、元の質問には関係ありません。

何が起こるかというと、あなたが使用するシーケンスは配列であり、 ドキュメントによると 、私のものを強調します:

配列型は、抽象基本型Arrayから派生した参照型です。 この型はIEnumerableおよび_IEnumerable<T>_を実装しているため、C#のすべての配列でforeach反復を使用できます。

同様に、 Arrayクラスの定義 を確認できます。

_public abstract class Array : ICloneable, IList, ICollection, 
    IEnumerable, IStructuralComparable, IStructuralEquatable
_

インターフェースのIEnumerableをご覧ください。

さらに興味深いことに、typeof(int[])の結果を調べると、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>)

追加の汎用インターフェースも表示されます。 (これは、なぜ_IList<int> a = new[] { 1, 2, 3 };_と書けるかを説明しています。)

同様に、 _List<T>__ConcurrentQueue<T>_ などは、IEnumerableおよび_Enumerable<T>_を実装します。

だからあなたがそれを言うとき:

また、配列はIEnumerableを実装しなくても機能します。

これは単に真実ではありません。配列IEnumerableを実装しています。はどうかと言うと:

IEnumerableを実装していませんが、foreachと完全に連携している次のクラスがあります。

実際のコードを見ることは興味深いでしょう(質問にあるコードでは配列を使用しています)。おそらく何が起こっているのでしょうか?あなたのクラスは、そのインターフェースの中でIEnumerableまたは_IEnumerable<T>_を明示的に宣言せずに、 _ICollection<T>_ などのより具体的なインターフェースを実装します。次に、IEnumerableから継承します:

_public interface ICollection<T> : IEnumerable<T>, IEnumerable
_

そうでなければ、まあ、あなたはおそらくkaiの答えに示されているケースにいるでしょう。

13

必須ではありません。 foreachキーワードは基本的にコードを「リライト」するだけなので、MoveNext()を呼び出してCurrentを参照するwhileループになりますが、インターフェースIEnumerableはt必要。これは、セクション_15.8.4 The foreach statement_の C#言語仕様 によってバックアップされます。

次のコードをコンパイルしてみてください。msbuildには何も言うことがありません。

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

foreachキーワードを使用するために必要なのは、GetEnumerator()というメソッドがboolというメソッドを返すMoveNext()というメソッドがあることだけです。 ] _およびCurrentと呼ばれる(任意のタイプの)プロパティで、少なくとも可視のゲッター

LINQクエリ内包と同様に機能します。次のコードは、コンパイルしてスムーズに実行します。

_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"
    }
}
_
8
sara

エリックリッパートは実際にこれについて一片を持っています:

https://blogs.msdn.Microsoft.com/ericlippert/2011/06/30/following-the-pattern/

エリックの説明:

コレクションのタイプにはGetEnumeratorというパブリックメソッドが必要であり、Currentと呼ばれるパブリックプロパティゲッターとboolを返すパブリックメソッドMoveNextを持つタイプを返す必要があります。

興味深いことに(そしてこの記事で言及しているように)、これはC#1.0のジェネリックの欠如の遺物です。 C#コンパイラは、ダックタイピングを使用して、列挙型がObjectを返すのではなく、コレクションに適切なタイプを返し、値タイプのボックス化/ボックス化解除のコストを発生させました。

私がまだ大学にいたときにこれについて学び、C#を学んだことを覚えています。そのとき、私は気分が悪くなりました。今、私はそれが一種の賢いことを見つけました!

7
MetaFight