web-dev-qa-db-ja.com

foreachがGetEnumerator拡張メソッドを見つけられないのはなぜですか?

私はいくつかのコードをより読みやすくしようとしています。たとえば、foreach(var row in table) {...}ではなくforeach(DataRow row in table.Rows) {...}

これを行うために、拡張メソッドを作成しました。

namespace System.Data {
    public static class MyExtensions {
        public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
            foreach ( DataRow r in tbl.Rows ) yield return r;
        }
    }
}

しかし、コンパイラはそれでもforeach statement cannot operate on variables of type 'System.Data.DataTable' because 'System.Data.DataTable' does not contain a public definition for 'GetEnumerator'をスローします。

拡張メソッドが適切に実装されていることを確認するために、代わりに次のコードを試しましたが、コンパイラーは問題ありませんでした。

for ( IEnumerator<DataRow> enm = data.GetEnumerator(); enm.MoveNext(); ) {
    var row = enm.Current;
    ...
}

IEnumeratorまたはIEnumerator<DataRow>が実装されていないためだと言う前に、以下がコンパイルされることを考慮してください。

public class test {
    public void testMethod() {
        foreach ( var i in new MyList( 1, 'a', this ) ) { }
    }
}
public class MyList {
    private object[] _list;
    public MyList( params object[] list ) { _list = list; }
    public IEnumerator<object> GetEnumerator() { foreach ( var o in _list ) yield return o; }
}
20
jshall

これまでのところ、他の回答には多くの混乱があります。 (プレストンギロットの答えはかなり良いですが、実際にはここで何が起こっているのかを指で触れません。)明確にしようと思います。

最初オフ、あなたは単に運が悪いです。 C#では、foreachステートメントで使用されるコレクションが次のいずれかである必要があります。

  1. 必要なパターンに一致するpublicGetEnumeratorを実装します。
  2. IEnumerableを実装します(そしてもちろん、IEnumerable<T>にはIEnumerableが必要です)
  3. 動的になります。その場合は、缶を蹴り出し、実行時に分析を行います。

結論として、コレクションタイプはGetEnumeratorを何らかの方法で実際に実装する必要があります。拡張メソッドを提供しても、それは削減されません。

これは残念です。私の意見では、C#チームがC#3に拡張メソッドを追加したとき、拡張メソッドを検討するためにforeach(そしておそらくusing!)などの既存の機能を変更する必要がありました。ただし、C#3のリリースサイクル中のスケジュールは非常に厳しく、LINQが時間どおりに実装されなかった余分な作業項目は削減される可能性がありました。この点についてデザインチームが言ったことを正確に思い出せず、メモもありません。

この不幸な状況は、言語が成長し進化するという事実の結果です。古いバージョンは時代のニーズに合わせて設計されており、新しいバージョンはその基盤の上に構築する必要があります。逆に、C#1.0に拡張メソッドとジェネリックスがあった場合、foreachループはLINQ:のように単純な構文変換として設計できたはずです。しかし、そうではありませんでした。そして今、私たちは、プレジェネリック、プレエクステンションメソッドの設計の遺産にとらわれています。

Second、他の回答やコメントには、foreachを機能させるために正確に何が必要かについての誤った情報があるようです。 IEnumerableを実装する必要はありません。このよく誤解されている機能の詳細については、 このテーマに関する私の記事 を参照してください。

3番目、この動作が実際に仕様によって正当化されるかどうかについていくつかの疑問があるようです。です。仕様では、この場合、拡張メソッドが考慮されないことを明示的に指定していません。これは残念なことです。ただし、仕様は何が起こるかについて非常に明確です。

コンパイラーは、GetEnumeratorに対してメンバールックアップを実行することから始めます。メンバールックアップアルゴリズムはセクション7.3で詳細に説明されており、メンバールックアップは拡張メソッドを考慮せず、実際のみを考慮しますメンバー。拡張メソッドは、通常のオーバーロード解決が失敗した後にのみ考慮され、まだオーバーロード解決に到達していません。 (はい、拡張メソッドはメンバーアクセスによって考慮されますが、メンバーアクセスおよびメンバールックアップ異なる操作です。)

メンバールックアップがメソッドグループを見つけられなかった場合、パターンの照合は失敗します。したがって、コンパイラはアルゴリズムの過負荷解決部分に進むことはなく、したがって拡張メソッドを検討する機会はありません。

したがって、説明する動作は、指定された動作と一致しています。

コンパイラがforeachステートメントを分析する方法を正確に理解したい場合は、仕様のセクション8.8.4を非常に注意深く読むことをお勧めします。

4番目、他の方法でプログラムに付加価値を付けることに時間を費やすことをお勧めします。の魅力的なメリット

foreach (var row in table)

以上

foreach(var row in table.Rows)

開発者にとっては小さく、顧客には見えません。すでに完全に明確なコードを5文字短くするのではなく、新しい機能の追加、バグの修正、パフォーマンスの分析に時間を費やしてください。

40
Eric Lippert

テストクラスのGetEnumeratorメソッドは静的ではなく、拡張メソッドは静的です。これもコンパイルされません:

class test
{
}

static class x
{
    public static IEnumerator<object> GetEnumerator(this test t) { return null; }
}

class Program
{
    static void Main(string[] args)
    {
        foreach (var i in new test()) {  }
    }
}

Foreach構文の糖衣構文が機能するためには、クラスがパブリックGetEnumerator instanceメソッドを公開する必要があります。

4
Preston Guillot

Foreachステートメント内で、コンパイラーはGetEnumeratorのインスタンスメソッドを探しています。したがって、タイプ(ここではDataTable)はIEnumerableを実装する必要があります。静的であるため、代わりに拡張メソッドが見つかることはありません。 foreachに拡張メソッドの名前を書き込む必要があります。

namespace System.Data {
    public static class MyExtensions {
        public static IEnumerable<DataRow> GetEnumerator( this DataTable table ) {
            foreach ( DataRow r in table.Rows ) yield return r;
        }
    }
}

foreach(DataRow row in table.GetEnumerator())
  .....

混乱を避けるために、拡張メソッドには別の名前を使用することをお勧めします。たぶんGetRows()のようなもの

0
Matthias

いくつかのオフトピック:もっと読みやすい書き込みをしたい場合

foreach ( DataRow r in tbl.Rows ) yield return r;

なので

foreach (DataRow row in tbl.Rows) 
{
    yield return row;
}

今あなたの問題に..これを試してみてください

    public static IEnumerable<T> GetEnumerator<T>(this DataTable table)
    {
        return table.Rows.Cast<T>();
    }
0
Lucas

拡張機能を介してGetEnumeratorをC#に追加する現在の提案 https://github.com/dotnet/csharplang/issues/3194

0
Ben Adams

拡張機能は次のものと同等です。

    public static IEnumerable<TDataRow> GetEnumerator<TDataRow>( this DataTable tbl ) {
        foreach ( TDataRow r in tbl.Rows ) yield return r;
    }

GetEnumerator<TDataRow>GetEnumeratorと同じ方法ではありません

これはうまく機能します:

    public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
        foreach (DataRow r in tbl.Rows ) yield return r;
    }
0
Amy B