Подтвердить что ты не робот

Почему 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; }
}
4b9b3361

Ответ 1

До сих пор существует много путаницы в других ответах. (Хотя ответ Престона Гийо довольно хорош, он на самом деле не указывает на то, что происходит здесь.) Позвольте мне попытаться пояснить.

Сначала, вам просто не повезло. С# требует, чтобы коллекция, используемая в инструкции foreach, также:

  • Внедрить общедоступный GetEnumerator, который соответствует требуемому шаблону.
  • Реализация IEnumerable (и, конечно, IEnumerable<T> требует IEnumerable)
  • Быть динамичным, и в этом случае мы просто пинаем вниз по дороге и выполняем анализ во время выполнения.

Результатом является то, что тип коллекции должен фактически реализовать GetEnumerator так или иначе. Предоставление метода расширения не сокращает его.

Это несчастливо. На мой взгляд, когда команда С# добавила методы расширения в С# 3, они должны были изменить существующие функции, такие как foreach (и, возможно, даже using!), Чтобы рассмотреть методы расширения. Тем не менее, график был чрезвычайно жестким во время цикла выпуска С# 3, и любые дополнительные рабочие элементы, которые не обеспечивали реализацию LINQ вовремя, скорее всего, будут сокращены. Я не помню точно, что сказала команда разработчиков по этому вопросу, и у меня больше нет моих заметок.

Эта неудачная ситуация является результатом того, что языки растут и развиваются; старые версии разработаны для нужд своего времени, и новые версии должны основываться на этом фундаменте. Если, контрфактивно, у С# 1.0 были методы расширения и обобщения, то цикл foreach мог быть спроектирован как LINQ: как простое синтаксическое преобразование. Но это было не так, и теперь мы застряли в наследстве пред-генерического дизайна с предварительным расширением.

Второй, кажется, есть некоторая дезинформация в других ответах и ​​комментариях о том, что именно требуется для работы foreach. Вам не требуется реализовать IEnumerable. Более подробную информацию об этой часто ошибочной функции см. В в моей статье по теме.

Третий, похоже, возникает вопрос о том, действительно ли это поведение оправдано спецификацией. Это. В спецификации явно не указано, что в этом случае методы расширения не рассматриваются, что является неудачным. Тем не менее, в спецификации очень ясно, что происходит:

Компилятор начинает с поиска членов для GetEnumerator. Алгоритм поиска элементов подробно описан в разделе 7.3, а поиск элементов не рассматривает методы расширения, только действительные члены. Методы расширения рассматриваются только после того, как правильное разрешение перегрузки не удалось, и мы еще не получили разрешение на перегрузку. (И да, методы расширения рассматриваются путем доступа членов, но доступ к члену и поиск членов - это разные операции.)

Если поиск элемента не может найти группу методов, попытка выполнить совпадение с шаблоном не выполняется. Поэтому компилятор никогда не переходит к части разрешения перегрузки алгоритма и поэтому никогда не имеет возможности рассматривать методы расширения.

Поэтому поведение, которое вы описываете, согласуется с указанным поведением.

Я советую вам внимательно прочитать раздел 8.8.4 спецификации, если вы хотите точно понять, как компилятор анализирует оператор foreach.

Четвертый, я рекомендую вам потратить свое время на добавление ценности вашей программе каким-то другим способом. Убедительная польза от

foreach (var row in table)

над

foreach(var row in table.Rows)

является крошечным для разработчика и невидимым для клиента. Проведите время, добавляя новые функции или исправляя ошибки или анализируя производительность, вместо того, чтобы сделать уже совершенно четкий код на пять символов короче.

Ответ 2

Метод 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.

Ответ 3

некоторый оффтоп: если вы хотите сделать это более читаемым, напишите

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

Ответ 4

Ваше расширение эквивалентно:

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

Ответ 5

Внутри оператора 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()

Ответ 6

Коллекция объектов в foreach должна реализовать System.Collections.IEnumerable или System.Collections.Generic.IEnumerable<T>.

Если у вас есть очень сильное желание включить это, вы можете создать класс-оболочку, который реализует IEnumerable и имеет указатель на вас DataTable. В качестве альтернативы вы можете наследовать DataTable в новом классе и реализовать IEnumerable.

Чтение кода чаще всего является личным предпочтением. Я лично считаю, что ваши изменения в выражении foreach менее читабельны (но я уверен, что многие из тех, кто согласен с вами).