Реализация интерфейсов IEnumerable и IEnumerator

Для циклического обращения к элементам коллекции зачастую проще (да и лучше) организовать цикл foreach, чем пользоваться непосредственно методами интерфейса IEnumerator. Тем не менее ясное представление о принципе действия подобных интерфейсов важно иметь по еще одной причине: если требуется создать класс, содержащий объекты, перечисляемые в цикле foreach, то в этом классе следует реализовать интерфейсы IEnumerator и IEnumerable. Иными словами, для того чтобы обратиться к объекту определяемого пользователем класса в цикле foreach, необходимо реализовать интерфейсы IEnumerator и IEnumerable в их обобщенной или необобщенной форме. Правда, сделать это будет нетрудно, поскольку оба интерфейса не очень велики.
Ниже приведен пример, в котором класс MyInt реализует данные интерфейсы:
using System; using System.Collections; namespace ConsoleApplication1 < class MyInt : IEnumerable, IEnumerator < int[] ints = < 12, 13, 1, 4 >; int index = -1; // Реализуем интерфейс IEnumerable public IEnumerator GetEnumerator() < return this; >// Реализуем интерфейс IEnumerator public bool MoveNext() < if (index == ints.Length - 1) < Reset(); return false; >index++; return true; > public void Reset() < index = -1; >public object Current < get < return ints[index]; >> > class Program < static void Main() < MyInt mi = new MyInt(); foreach (int i in mi) Console.Write(i+"\t"); Console.ReadLine(); >> >
IEnumerable Интерфейс
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Предоставляет перечислитель, который поддерживает простой перебор элементов в указанной коллекции.
generic public interface class IEnumerable : System::Collections::IEnumerable
public interface IEnumerable : System.Collections.IEnumerable
public interface IEnumerable : System.Collections.IEnumerable
type seq = interface interface IEnumerable
Public Interface IEnumerable(Of Out T) Implements IEnumerable
Public Interface IEnumerable(Of T) Implements IEnumerable
Параметры типа
Тип объектов для перечисления.
Это ковариантный параметр типа. Это означает, что вы можете использовать любой из указанных типов или любой тип, являющийся более производным. Дополнительные сведения о ковариантности и контрвариантности см. в статье Ковариантность и контрвариантность в универсальных шаблонах.
Производный
Реализации
Примеры
using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Linq; public class App < // Exercise the Iterator and show that it's more // performant. public static void Main() < TestStreamReaderEnumerable(); Console.WriteLine("---"); TestReadingFile(); >public static void TestStreamReaderEnumerable() < // Check the memory before the iterator is used. long memoryBefore = GC.GetTotalMemory(true); IEnumerablestringsFound; // Open a file with the StreamReaderEnumerable and check for a string. try < stringsFound = from line in new StreamReaderEnumerable(@"c:\temp\tempFile.txt") where line.Contains("string to search for") select line; Console.WriteLine("Found: " + stringsFound.Count()); >catch (FileNotFoundException) < Console.WriteLine(@"This example requires a file named C:\temp\tempFile.txt."); return; >// Check the memory after the iterator and output it to the console. long memoryAfter = GC.GetTotalMemory(false); Console.WriteLine("Memory Used With Iterator = \t" + string.Format(((memoryAfter - memoryBefore) / 1000).ToString(), "n") + "kb"); > public static void TestReadingFile() < long memoryBefore = GC.GetTotalMemory(true); StreamReader sr; try < sr = File.OpenText("c:\\temp\\tempFile.txt"); >catch (FileNotFoundException) < Console.WriteLine(@"This example requires a file named C:\temp\tempFile.txt."); return; >// Add the file contents to a generic list of strings. List fileContents = new List(); while (!sr.EndOfStream) < fileContents.Add(sr.ReadLine()); >// Check for the string. var stringsFound = from line in fileContents where line.Contains("string to search for") select line; sr.Close(); Console.WriteLine("Found: " + stringsFound.Count()); // Check the memory after when the iterator is not used, and output it to the console. long memoryAfter = GC.GetTotalMemory(false); Console.WriteLine("Memory Used Without Iterator = \t" + string.Format(((memoryAfter - memoryBefore) / 1000).ToString(), "n") + "kb"); > > // A custom class that implements IEnumerable(T). When you implement IEnumerable(T), // you must also implement IEnumerable and IEnumerator(T). public class StreamReaderEnumerable : IEnumerable < private string _filePath; public StreamReaderEnumerable(string filePath) < _filePath = filePath; >// Must implement GetEnumerator, which returns a new StreamReaderEnumerator. public IEnumerator GetEnumerator() < return new StreamReaderEnumerator(_filePath); >// Must also implement IEnumerable.GetEnumerator, but implement as a private method. private IEnumerator GetEnumerator1() < return this.GetEnumerator(); >IEnumerator IEnumerable.GetEnumerator() < return GetEnumerator1(); >> // When you implement IEnumerable(T), you must also implement IEnumerator(T), // which will walk through the contents of the file one line at a time. // Implementing IEnumerator(T) requires that you implement IEnumerator and IDisposable. public class StreamReaderEnumerator : IEnumerator < private StreamReader _sr; public StreamReaderEnumerator(string filePath) < _sr = new StreamReader(filePath); >private string _current; // Implement the IEnumerator(T).Current publicly, but implement // IEnumerator.Current, which is also required, privately. public string Current < get < if (_sr == null || _current == null) < throw new InvalidOperationException(); >return _current; > > private object Current1 < get < return this.Current; >> object IEnumerator.Current < get < return Current1; >> // Implement MoveNext and Reset, which are required by IEnumerator. public bool MoveNext() < _current = _sr.ReadLine(); if (_current == null) return false; return true; >public void Reset() < _sr.DiscardBufferedData(); _sr.BaseStream.Seek(0, SeekOrigin.Begin); _current = null; >// Implement IDisposable, which is also implemented by IEnumerator(T). private bool disposedValue = false; public void Dispose() < Dispose(disposing: true); GC.SuppressFinalize(this); >protected virtual void Dispose(bool disposing) < if (!this.disposedValue) < if (disposing) < // Dispose of managed resources. >_current = null; if (_sr != null) < _sr.Close(); _sr.Dispose(); >> this.disposedValue = true; > ~StreamReaderEnumerator() < Dispose(disposing: false); >> // This example displays output similar to the following: // Found: 2 // Memory Used With Iterator = 33kb // --- // Found: 2 // Memory Used Without Iterator = 206kb
Imports System.IO Imports System.Collections Imports System.Collections.Generic Imports System.Linq Public Module App ' Excercise the Iterator and show that it's more performant. Public Sub Main() TestStreamReaderEnumerable() Console.WriteLine("---") TestReadingFile() End Sub Public Sub TestStreamReaderEnumerable() ' Check the memory before the iterator is used. Dim memoryBefore As Long = GC.GetTotalMemory(true) Dim stringsFound As IEnumerable(Of String) ' Open a file with the StreamReaderEnumerable and check for a string. Try stringsFound = from line in new StreamReaderEnumerable("c:\temp\tempFile.txt") where line.Contains("string to search for") select line Console.WriteLine("Found: ", stringsFound.Count()) Catch e As FileNotFoundException Console.WriteLine("This example requires a file named C:\temp\tempFile.txt.") Return End Try ' Check the memory after the iterator and output it to the console. Dim memoryAfter As Long = GC.GetTotalMemory(false) Console.WriteLine("Memory Used with Iterator = kb", (memoryAfter - memoryBefore)\1000, vbTab) End Sub Public Sub TestReadingFile() Dim memoryBefore As Long = GC.GetTotalMemory(true) Dim sr As StreamReader Try sr = File.OpenText("c:\temp\tempFile.txt") Catch e As FileNotFoundException Console.WriteLine("This example requires a file named C:\temp\tempFile.txt.") Return End Try ' Add the file contents to a generic list of strings. Dim fileContents As New List(Of String)() Do While Not sr.EndOfStream fileContents.Add(sr.ReadLine()) Loop ' Check for the string. Dim stringsFound = from line in fileContents where line.Contains("string to search for") select line sr.Close() Console.WriteLine("Found: ", stringsFound.Count()) ' Check the memory after when the iterator is not used, and output it to the console. Dim memoryAfter As Long = GC.GetTotalMemory(False) Console.WriteLine("Memory Used without Iterator = kb", (memoryAfter - memoryBefore)\1000, vbTab) End Sub End Module ' A custom class that implements IEnumerable(T). When you implement IEnumerable(T), ' you must also implement IEnumerable and IEnumerator(T). Public Class StreamReaderEnumerable : Implements IEnumerable(Of String) Private _filePath As String Public Sub New(filePath As String) _filePath = filePath End Sub ' Must implement GetEnumerator, which returns a new StreamReaderEnumerator. Public Function GetEnumerator() As IEnumerator(Of String) _ Implements IEnumerable(Of String).GetEnumerator Return New StreamReaderEnumerator(_filePath) End Function ' Must also implement IEnumerable.GetEnumerator, but implement as a private method. Private Function GetEnumerator1() As IEnumerator _ Implements IEnumerable.GetEnumerator Return Me.GetEnumerator() End Function End Class ' When you implement IEnumerable(T), you must also implement IEnumerator(T), ' which will walk through the contents of the file one line at a time. ' Implementing IEnumerator(T) requires that you implement IEnumerator and IDisposable. Public Class StreamReaderEnumerator : Implements IEnumerator(Of String) Private _sr As StreamReader Public Sub New(filePath As String) _sr = New StreamReader(filePath) End Sub Private _current As String ' Implement the IEnumerator(T).Current Publicly, but implement ' IEnumerator.Current, which is also required, privately. Public ReadOnly Property Current As String _ Implements IEnumerator(Of String).Current Get If _sr Is Nothing OrElse _current Is Nothing Throw New InvalidOperationException() End If Return _current End Get End Property Private ReadOnly Property Current1 As Object _ Implements IEnumerator.Current Get Return Me.Current End Get End Property ' Implement MoveNext and Reset, which are required by IEnumerator. Public Function MoveNext() As Boolean _ Implements IEnumerator.MoveNext _current = _sr.ReadLine() if _current Is Nothing Then Return False Return True End Function Public Sub Reset() _ Implements IEnumerator.Reset _sr.DiscardBufferedData() _sr.BaseStream.Seek(0, SeekOrigin.Begin) _current = Nothing End Sub ' Implement IDisposable, which is also implemented by IEnumerator(T). Private disposedValue As Boolean = False Public Sub Dispose() _ Implements IDisposable.Dispose Dispose(disposing:=True) GC.SuppressFinalize(Me) End Sub Protected Overridable Sub Dispose(disposing As Boolean) If Not Me.disposedValue Then If disposing Then ' Dispose of managed resources. End If _current = Nothing If _sr IsNot Nothing Then _sr.Close() _sr.Dispose() End If End If Me.disposedValue = True End Sub Protected Overrides Sub Finalize() Dispose(disposing:=False) End Sub End Class ' This example displays output similar to the following: ' Found: 2 ' Memory Used With Iterator = 33kb ' --- ' Found: 2 ' Memory Used Without Iterator = 206kb
Комментарии
Неуниверсационная версия этого интерфейса см. в разделе System.Collections.IEnumerable.
Примечания для тех, кто реализует этот метод
Чтобы оставаться совместимыми с методами, которые выполняют итерацию неуниверсических коллекций, IEnumerable реализует IEnumerable. Это позволяет передавать универсальную коллекцию в метод, который ожидает IEnumerable объект .
Методы
Возвращает перечислитель, выполняющий перебор элементов в коллекции.
Ienumerable c что это
Все, что необходимо начинающему и опытному программисту
Интерфейс IEnumerable.
В прошлом уроке вы узнали, что такое интерфейсы, поделили их на пользовательские и на встроенные в библиотеку .NET Framework(например, ICloneable). Сейчас настало время познакомиться с ещё двумя стандартными интерфейсами IEnumerable,IEnumerator. Для понимания, зачем они нужны, смоделируем ситуацию. Предположим, есть некий класс Shop(в переводе на русский — магазин) в нем внутри есть массив объектов Radio (то есть массив радиоприёмников). Наверняка было бы достаточно удобно обращаться к внутренним объектам Radio внутри класса Shop, используя конструкцию foreach.Например, так:
using System; class Radio < . >class Shop < private Radio[]arr; public Shop()< arr = new Radio[2]; arr[0] = new Radio("Sony",300); arr[1] = new Radio("Samsung",200); . >> class Sample < public static void Main()< Shop arr = new Shop(); foreach(Radio r in arr)< r.Sound(); >. > >
Однако при попытке откомпилировать такую программу возникнет ошибка компиляции, которая скажет, что мы не реализовали метод GetEnumerator интерфейса IEnumerable, который находится в пространстве имен System.Collections. Для её решения придется класс Shop отнаследовать от интерфейса IEnumerable (используется для прохода по коллекции) и создать тело для требуемого метода. После изменения получим:
// отнаследовали от IEnumerable class Shop:IEnumerable < private Radio[]arr; public Shop()< arr = new Radio[2]; arr[0] = new Radio("Sony",300); arr[1] = new Radio("Samsung",200); . >// метод, который возвращает нумератор для прохода // по коллекции объектов Radio public IEnumerator GetEnumerator() < return (IEnumerator)this >>
После этого foreach уже будет компилироваться, но для того чтобы проход по Radio состоялся нужно реализовать методы интерфейса IEnumerator, которые неявно будут вызываться при выполнении foreach.В интерфейсе IEnumerator всего 2 метода и 1 свойство.
Методы
bool MoveNext();// переход на следующий элемент коллекции void Reset();// переход на начало коллекции.
Под коллекцией подразумевается набор элементов какого - типа
Свойство
object Current // возвращает значение текущего элемента коллекции
Наш класс — контейнер обязан реализовать указанные выше методы и свойства. Рассмотрим программу, показывающую данный принцип:
using System; using System.Collections; class Book < private string title; private string author; private double price; public void EnterBook()< Console.WriteLine("\nВведите название Книги:"); title = Console.ReadLine(); Console.WriteLine("\nВведите автора:"); author = Console.ReadLine(); Console.WriteLine("\nВведите цену книги:"); price = Convert.ToDouble(Console.ReadLine()); >public Book(string title,string author,double price) < this.title = title; this.author = author; this.price = price; >public Book() < this.title = "Безыимянный"; this.author = "Иван Петров"; this.price = 0; >public string Title < get < return title; >set < title = value; >> public string Author < get < return author; >set < author = value; >> public double Price < get < return price; >set < price = value; >> public void Print() < Console.WriteLine("Автор:Название Книги: Цена:",author,title,price); > > class Library:IEnumerable,IEnumerator // наследование от обоих интерфесов < private Book[] obj; private string address; private string name; int curpos = -1; // индекс текущей позиции в массиве книг public Library() < address = "Украина,город Одесса, улица Троицкая 4"; name = "Библиотека Дружбы Народов"; obj = new Book[1]; obj[0] = new Book("Война и Мир","Лев Толстой",50); >public Library(string address,string name,int len) < this.address = address; this.name = name; obj = new Book[len]; for(int i = 0;i> public float QuantityOfBooks < get < return obj.Length; >> public string Address < get < return address; >set < address = value; >> public string Name < get < return name; >set < name = value; >> public void EnterBooks() < for(int i = 0;i> public void ShowBooks() < for(int i = 0;i> public IEnumerator GetEnumerator() < // возврат нумератора для доступа к коллекции return (IEnumerator)this; >public void Reset() < curpos = -1; // установка текущей позиции на начало массива книг. >public object Current < // Возврат текущей книги из массива книг get< return obj[curpos]; >> public bool MoveNext()/ переход на следующий элемент в коллекции if(curpos return false; > > class Sample < enum Cars; static void Main() < try < Library lib = new Library("Украина,город Одесса, улица Большая Арнаутская 78",
"Библиотека имени Остапа Бендера",2); lib.EnterBooks(); foreach(Book b in lib) < b.Print(); >lib.Reset(); Console.WriteLine("\n\n\n"); foreach(Book b1 in lib) < b1.Print(); >> catch(Exception ex) < Console.WriteLine(ex.Message); >Console.Read(); > >
Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru
Ienumerable c что это
Как мы увидели, основной для большинства коллекций является реализация интерфейсов IEnumerable и IEnumerator. Благодаря такой реализации мы можем перебирать объекты в цикле foreach :
foreach(var item in перечислимый_объект)
Перебираемая коллекция должна реализовать интерфейс IEnumerable.
Интерфейс IEnumerable имеет метод, возвращающий ссылку на другой интерфейс — перечислитель:
public interface IEnumerable
А интерфейс IEnumerator определяет функционал для перебора внутренних объектов в контейнере:
public interface IEnumerator < bool MoveNext(); // перемещение на одну позицию вперед в контейнере элементов object Current // текущий элемент в контейнере void Reset(); // перемещение в начало контейнера >
Метод MoveNext() перемещает указатель на текущий элемент на следующую позицию в последовательности. Если последовательность еще не закончилась, то возвращает true. Если же последовательность закончилась, то возвращается false.
Свойство Current возвращает объект в последовательности, на который указывает указатель.
Метод Reset() сбрасывает указатель позиции в начальное положение.
Каким именно образом будет осуществляться перемещение указателя и получение элементов зависит от реализации интерфейса. В различных реализациях логика может быть построена различным образом.
Например, без использования цикла foreach перебирем массив с помощью интерфейса IEnumerator:
using System.Collections; string[] people = ; IEnumerator peopleEnumerator = people.GetEnumerator(); // получаем IEnumerator while (peopleEnumerator.MoveNext()) // пока не будет возвращено false < string item = (string)peopleEnumerator.Current; // получаем элемент на текущей позиции Console.WriteLine(item); >peopleEnumerator.Reset(); // сбрасываем указатель в начало массива
Реализация IEnumerable и IEnumerator
Рассмотрим простешую реализацию IEnumerable на примере:
using System.Collections; Week week = new Week(); foreach (var day in week) < Console.WriteLine(day); >class Week : IEnumerable < string[] days = < "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" >; public IEnumerator GetEnumerator() => days.GetEnumerator(); >
В данном случае класс Week, который представляет неделю и хранит все дни недели, реализует интерфейс IEnumerable. Однако в данном случае мы поступили очень просто — вместо реализации IEnumerator мы просто возвращаем в методе GetEnumerator объект IEnumerator для массива.
public IEnumerator GetEnumerator() => days.GetEnumerator();
Благодаря этому мы можем перебрать все дни недели в цикле foreach.
В то же время стоит отметить, что для перебора коллекции через foreach в принципе необязательно реализовать интерфейс IEnumerable. Достаточно в классе определить публичный метод GetEnumerator , который бы возвращал объект IEnumerator. Например:
class Week < string[] days = < "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" >; public IEnumerator GetEnumerator() =>days.GetEnumerator(); >
Однако это было довольно просто — мы просто используем уже готовый перчислитель массива. Однако, возможно, потребуется задать свою собственную логику перебора объектов. Для этого реализуем интерфейс IEnumerator :
using System.Collections; class WeekEnumerator : IEnumerator < string[] days; int position = -1; public WeekEnumerator(string[] days) =>this.days = days; public object Current < get < if (position == -1 || position >= days.Length) throw new ArgumentException(); return days[position]; > > public bool MoveNext() < if (position < days.Length - 1) < position++; return true; >else return false; > public void Reset() => position = -1; > class Week < string[] days = < "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" >; public IEnumerator GetEnumerator() => new WeekEnumerator(days); >
Здесь теперь класс Week использует не встроенный перечислитель, а WeekEnumerator, который реализует IEnumerator.
Ключевой момент при реализации перечислителя — перемещения указателя на элемент. В классе WeekEnumerator для хранения текущей позиции определена переменная position. Следует учитывать, что в самом начале (в исходном состоянии) указатель должен указывать на позицию условно перед первым элементом. Когда будет производиться цикл foreach, то данный цикл вначале вызывает метод MoveNext и фактически перемещает указатель на одну позицию в перед и только затем обращается к свойству Current для получения элемента в текущей позиции.
Затем в программе мы можем аналогичным образом перебирать объект Week с помощью цикла foreach:
Week week = new Week(); foreach(var day in week)
Обобщенная версия IEnumerator
В примерах выше использовались необобщенные версии интерфейсов, однако мы также можем использовать их обобщенные двойники:
using System.Collections; class WeekEnumerator : IEnumerator < string[] days; int position = -1; public WeekEnumerator(string[] days) =>this.days = days; public string Current < get < if (position == -1 || position >= days.Length) throw new ArgumentException(); return days[position]; > > object IEnumerator.Current => Current; public bool MoveNext() < if (position < days.Length - 1) < position++; return true; >else return false; > public void Reset() => position = -1; public void Dispose() < >> class Week < string[] days = < "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" >; public IEnumerator GetEnumerator() => new WeekEnumerator(days); >
В данном случае реализуем интерфейс IEnumerator , соответственно в свойстве Current нам надо возвратить объект string. В этом случае при переборе в цикле foreach перебираемые объекты будут автоматически представлять тип string :
Week week = new Week(); foreach (string day in week)