Как сделать класс ienumerable

Добавил пользователь Алексей Ф.
Обновлено: 10.09.2024

Клонирование — это создание копии объекта. Копия объекта называется клоном. Как известно, при присваивании одного объекта ссылочного типа другому копируется ссылка, а не сам объект. Если необходимо скопировать в другую область памяти поля объекта, можно воспользоваться методом MemberwiseClone , который любой объект наследует от класса object . При этом объекты, на которые указывают поля объекта, в свою очередь являющиеся ссылками, не копируются. Это называется поверхностным клонированием.

Для создания полностью независимых объектов необходимо глубокое клонирование, когда в памяти создается дубликат всего дерева объектов, то есть объектов, на которые ссылаются поля объекта, поля полей, и так далее. Алгоритм глубокого клонирования весьма сложен, поскольку требует рекурсивного обхода всех ссылок объекта и отслеживания циклических зависимостей.

Объект, имеющий собственные алгоритмы клонирования, должен объявляться как наследник интерфейса ICloneable и переопределять его единственный метод Clone . В листинге 9.4 приведен пример создания поверхностной копии объекта класса Monster с помощью метода MemberwiseClone , а также реализован интерфейс ICloneable . В демонстрационных целях в имя клона объекта добавлено слово "Клон".

Объект Х ссылается на ту же область памяти, что и объект Вася . Следовательно, если мы внесем изменения в один из этих объектов, это отразится на другом. Объекты Y и Z , созданные путем клонирования, обладают собственными копиями значений полей и независимы от исходного объекта.

Перебор объектов (интерфейс IEnumerable) и итераторы

Интерфейс IEnumerable ( перечислимый ) определяет всего один метод — GetEnumerator , возвращающий объект типа IEnumerator ( перечислитель ), который можно использовать для просмотра элементов объекта.

Интерфейс IEnumerator задает три элемента:

  • свойство Current , возвращающее текущий элемент объекта;
  • метод MoveNext , продвигающий перечислитель на следующий элемент объекта;
  • метод Reset , устанавливающий перечислитель в начало просмотра.

Цикл foreach использует эти методы для перебора элементов, из которых состоит объект.

Таким образом, если требуется, чтобы для перебора элементов класса мог применяться цикл foreach , необходимо реализовать четыре метода: GetEnumerator , Current , MoveNext и Reset . Это не интересная работа, а выполнять ее приходится часто, поэтому в версию 2.0 были введены средства, облегчающие выполнение перебора в объекте — итераторы.

Итератор представляет собой блок кода, задающий последовательность перебора элементов объекта. На каждом проходе цикла foreach выполняется один шаг итератора, заканчивающийся выдачей очередного значения. Выдача значения выполняется с помощью ключевого слова yield .

Рассмотрим создание итератора на примере (листинг 9.5). Пусть требуется создать объект, содержащий боевую группу экземпляров типа Monster .

Все, что требуется сделать в версии 2.0 для поддержки перебора — указать, что класс реализует интерфейс IEnumerable (оператор 1), и описать итератор (оператор 2). Доступ к нему может быть осуществлен через методы MoveNext и Current интерфейса IEnumerator .

Преимущество использования итераторов заключается в том, что для одного и того же класса можно задать различный порядок перебора элементов. В листинге 9.6 описаны две дополнительные стратегии перебора элементов класса Stado , введенного в листинге 9.5 — перебор в обратном порядке и выборка только тех объектов, которые являются экземплярами класса Monster .

Теперь, когда вы получили представление об итераторах, рассмотрим их более формально. Блок итератора синтаксически представляет собой обычный блок и может встречаться в теле метода, операции или части get свойства, если соответствующее возвращаемое значение имеет тип IEnumerable или IEnumerator .

В теле блока итератора могут встречаться две конструкции:

  • yield return формирует значение, выдаваемое на очередной итерации;
  • yield break сигнализирует о завершении итерации.

Ключевое слово yield имеет специальное значение для компилятора только в этих конструкциях.

Код блока итератора выполняется не так, как обычные блоки. Компилятор формирует служебный объект-перечислитель, при вызове метода MoveNext которого выполняется код блока итератора, выдающий очередное значение с помощью ключевого слова yield . Следующий вызов метода MoveNext объекта-перечислителя возобновляет выполнение блока итератора с момента, на котором он был приостановлен в предыдущий раз.

Ранее уже было показано, как использовать оператор foreach для удобного выполнения итерации по коллекции объектов, включая System.Array, ArrayList, List и т.п. Как это функционирует?

Каждая коллекция, которая должна работать с foreach, должна реализовать интерфейс IEnumerable или IEnumerable. Этот интерфейс используется foreach для получения объекта, знающего, как перечислить, или выполнить итерацию, по элементам коллекции.

Объект итератора, полученный от IEnumerable , должен реализовать интерфейс IEnumerator или IEnumerator. Обобщенные типы коллекций обычно реализуют IEnumerator , а объект перечислителя — IEnumerator . Интерфейс IEnumerable наследуется от IEnumerable, a IEnumerator — от IEnumerator. Это позволяет применять обобщенные коллекции там же, где используются необобщенные.

Строго говоря, типы коллекций не обязаны реализовывать перечислители, и пользователь может выполнять итерации в цикле for, если предусмотрена операция индекса, например, путем реализации интерфейса IList . Однако это не самый изящный подход.

После того, как будет продемонстрировано, насколько легко создавать перечислители с помощью блоков итераторов, вы поймете, что реализовать IEnumerable и IEnumerator — пара пустяков.

Если хотите, можете пропустить эту часть и перейти к следующему разделу, посвященному итераторам. При желании освежить в памяти тему реализации перечислителей читайте все.

Интерфейс IEnumerable предусмотрен для того, чтобы клиенты имели четко определенный способ получения перечислителя для коллекции. Ниже показано определение интерфейсов IEnumerable и IEnumerable:

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

Интерфейсы IEnumerator и IEnumerator выглядят следующим образом:

Эти два интерфейса реализуют член с одинаковой сигнатурой — в данном случае свойство Current. В случае реализации IEnumerator должен быть явно реализовано свойство IEnumerator .Current. К тому же обратите внимание, что IEnumerator реализует интерфейс IDisposable. Позже будет объясняться, чем это хорошо.

А теперь рассмотрим, как реализовать IEnumerable и IEnumerator для доморощенного типа коллекции. Хороший учитель всегда сначала показывает, как сделать что-то “трудным способом”, а только потом переходит к “легкому способу”. Такой прием очень полезен, поскольку позволяет понять, что происходит “за кулисами”. Когда вы понимаете, как работает внутренний механизм, то лучше подготовлены к тому, чтобы иметь дело с техническими нюансами “легкого способа”.

Рассмотрим пример реализации IEnumerable и IEnumerator “трудным способом” для доморощенной коллекции целых чисел. В примере будет показано, как реализовать обобщенные версии, поскольку это предполагает также реализацию необобщенных версий. Чтобы не загромождать пример, ICollection не реализуется, а все внимание сосредоточивается только на интерфейсах перечисления.

В более реальных случаях пользовательский класс коллекции наследуется от Collection и реализация IEnumerable получается бесплатно.

В этом примере внутренний массив МуСоLL инициализируется случайным набором целых чисел, так что перечислитель получает некоторые тестовые данные. Разумеется, в реальном контейнере должен быть реализован интерфейс ICollection , что позволит динамически наполнять коллекцию элементами.

Оператор foreach разворачивается в код, получающий перечислитель вызовом метода GetEnumerator на интерфейсе IEnumerable . Компилятор достаточно интеллектуален, чтобы использовать в данном случае IEnumerator .GetEnumerator вместо IEnumerator.GetEnumerator.

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

Несмотря на возможность явного создания и использования перечислителя, вместо него рекомендуется применять конструкцию foreach. В этом случае придется писать меньше кода, что означает меньшую вероятность непреднамеренного внесения ошибок.

Конечно, могут существовать веские причины для непосредственной манипуляции перечислителем. Например, перечислитель может реализовывать специфичные методы, которые должны вызываться в процессе прохода по коллекции. Если необходимо манипулировать перечислением напрямую, всегда делайте это внутри блока using, поскольку IEnumerator реализует IDisposable.

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

Если коллекция изменяется в процессе ее перечисления, то перечислитель становится семантически некорректным, и последующее его использование может привести к неопределенному поведению. Если необходимо предохранить целостность в таких ситуациях, то может понадобиться, чтобы перечислитель блокировал коллекцию через объект, предоставляемый свойством SyncRoot .

Очевидно, что блокировка должна быть получена в конструкторе перечислителя. Кроме того, эту блокировку нужно где-то снимать. Уже известно, что для выполнения такой детерминированной очистки необходимо реализовать интерфейс IDisposable. Именно в этом состоит причина реализации интерфейса IDisposable в IEnumerator .

Более того, код, сгенерированный оператором foreach, создает “за кулисами” блок try/finally, который вызывает Dispose на перечислителе внутри блока finally. Описанная техника в действии продемонстрирована в предыдущем примере.

Типы, производящие коллекции

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

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

Цикл foreach может выполнять какую-то длительную обработку каждого элемента, и при этом для кого-то другого коллекция окажется недоступной для модификаций.

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

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

Существующее эмпирическое правило гласит, что при возврате типов коллекций из типов посредством методов всегда должны возвращаться копии, а при обращении к ним через свойства — ссылки на оригиналы коллекций. Хотя это правило не является незыблемым и следовать ему не обязательно, в нем есть определенный семантический смысл.

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

Именно поэтому данное эмпирическое правило имеет хороший семантический смысл. Аналогичное семантическое разделение имеет смысл применять ко всем свойствам и методам внутри создаваемых типов.

Ну. если все, что вам нужно, это для кого-то "просто сделать эту черновую вещь перечислимой", вот и все.

Он ничего не перечисляет, но он перечислим.

Теперь я попытаюсь заняться чтением ума и задаться вопросом, хотите ли вы что-то вроде этого:

Я бы использовал класс без наследования:

Вопрос в том, что вы хотите перечислить?

Я думаю, вам нужен какой-то контейнер, содержащий элементы вашего типа ProfilePics так что

либо внутри такого класса:

или просто используя List

в тех местах, где вам нужен контейнер.

Если вы пропустили это: вот MSDN Docu для этого IEnumerable (еще несколько примеров)

Чтобы быть перечислимым классом, необходимо реализовать некоторую коллекцию. Я не вижу каких-либо свойств коллекции в вашем примере. Если вы хотите создать коллекцию профилей, переименуйте свой класс в "ProfiePic" и используйте "Список".

Если вы хотите открыть какое-либо свойство в качестве коллекции, сделайте его типа IEnumerable или List или другой коллекции.

Одной из удобных возможностей работать с коллекциями является перебор элементов по порядку их следования внутри коллекции, например, с помощью конструкции foreach.

Следующий пример выводит на консоль содержание массива ArrayList:

Но если попытаться применить foreach для перебора элементов коллекции, хранящей пользовательские классы, то это приведет к остановке компиляции, поскольку в такой коллекции не реализован пере числитель.

Что такое перечислитель?

Понятно, что перечислитель абстрактная сущность, поскольку богатство принципов объектно-ориентированного программирования, в частности инкапсуляции состоит в том, что методы перечисления могут быть по-разному реализованы, но всегда обязаны перечислять.

Для перечислений в универсальных (обобщенных) коллекциях используются обобщенные версии этих интерфейсов – IEnumarable и IEnumerator .

IEnumerable и IEnumerator

Интерфейс IEnumerable содержит метод возвращающий перечислитель – GetEnumerator():

Интерфейс IEnumerator содержит объявления методов перебора элементов коллекции:

Метод Reset() – Перемещает указатель перечисления в начало коллекции. Метод MoveNext() перемещает указатель на следующий элемент в коллекции. Свойство только для чтения Current позволяет получить объект, на который установлен указатель в текущий момент времени.

Класс, который организовывает перечисление должен явно наследоваться от интерфейса IEnumarable, класс реализующий перечислимую коллекцию должен явно наследоваться от IEnumerator.

Пример:

На первый взгляд реализация с возвратом IEnumerator из IEnumarable выглядит сложной и лишней. Но это только на первый взгляд. Отделение метода перечисления от перечислителя дает нам дает нам гибкость в определении собственных алгоритмов перечислений, ничего не зная о которых foreach будет проглатывать и исполнять

У меня есть следующая функция, в которой должен возвращать тип IEnumerable? Как я могу преобразовать список в IEnumerable? и вернуть пустой IEnumerable?

Не удается неявно преобразовать тип 'System.Collections.Generic.List в System.Collections.Generic.IEnumerable . Существует явное преобразование (вы пропустили приведение?)

error

3 ответа

Проблема не в разговоре List с IEnumerable . Потому что List реализует IEnumerable .

Ваша проблема в том, что общие параметры разные. Вы пытаетесь преобразовать List в IEnumerable . Куда:

  • T1 это QlasrService.EntityFramework.tblSoftwareImageTestPlan
  • T2 это QlasrService.Model.SchemaModels.LAP.SoftwareImageTestPlan

Простейшим решением будет картирование (либо вручную, либо автоматически). Автоматическое картирование очень просто. Добавить пакет Automapper nuget. Поместите эту строку где-нибудь в начале приложения:

И теперь ваш метод будет выглядеть так:

ПРИМЕЧАНИЕ. В вашем коде либо records не может иметь значение null , либо вы получите NullReferenceException при вызове ToList() . Так что if..else блок бесполезен в любом случае.

Проблема здесь не в том, что вам нужно конвертировать из List в IEnumerable .

Проблема в том, что вы пытаетесь преобразовать из List в IEnumerable

Это два совершенно разных типа из-за аргумента типа.

    Измените тип возвращаемого значения на IEnumerable

Сопоставьте объекты с SoftwareImageTestPlan , проецируя tblSoftwareImageTestPlan на SoftwareImageTestPlan :

Вы возвращаете два разных типа объекта:

  • tblSoftwareImageTestPlan - который находится в вашей модели Entity Framework
  • SoftwareImageTestPlan - который находится в ваших моделях схемы Qlasr

Поэтому, когда вы заявляете следующее:

Он будет жаловаться, что объект records не относится к типу SoftwareImageTestPlan . Поэтому вам необходимо преобразовать records в новый List , которого вы можете достичь с помощью LINQ projection .

Читайте также: