Getenumerator c как реализовать
Перейти к содержимому

Getenumerator c как реализовать

  • автор:

IEnumerable. Get Enumerator Метод

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

Возвращает перечислитель, который осуществляет итерацию по коллекции.

public: System::Collections::IEnumerator ^ GetEnumerator();
public System.Collections.IEnumerator GetEnumerator ();
abstract member GetEnumerator : unit -> System.Collections.IEnumerator
Public Function GetEnumerator () As IEnumerator
Возвращаемое значение

Объект IEnumerator, который используется для прохода по коллекции.

Примеры

В следующем примере кода демонстрируется реализация IEnumerable интерфейсов для пользовательской коллекции. В этом примере GetEnumerator не вызывается явным образом, но реализуется для поддержки foreach использования ( For Each в Visual Basic). Этот пример кода является частью более крупного примера для IEnumerable интерфейса .

using System; using System.Collections; // Simple business object. public class Person < public Person(string fName, string lName) < this.firstName = fName; this.lastName = lName; >public string firstName; public string lastName; > // Collection of Person objects. This class // implements IEnumerable so that it can be used // with ForEach syntax. public class People : IEnumerable < private Person[] _people; public People(Person[] pArray) < _people = new Person[pArray.Length]; for (int i = 0; i < pArray.Length; i++) < _people[i] = pArray[i]; >> // Implementation for the GetEnumerator method. IEnumerator IEnumerable.GetEnumerator() < return (IEnumerator) GetEnumerator(); >public PeopleEnum GetEnumerator() < return new PeopleEnum(_people); >> // When you implement IEnumerable, you must also implement IEnumerator. public class PeopleEnum : IEnumerator < public Person[] _people; // Enumerators are positioned before the first element // until the first MoveNext() call. int position = -1; public PeopleEnum(Person[] list) < _people = list; >public bool MoveNext() < position++; return (position < _people.Length); >public void Reset() < position = -1; >object IEnumerator.Current < get < return Current; >> public Person Current < get < try < return _people[position]; >catch (IndexOutOfRangeException) < throw new InvalidOperationException(); >> > > class App < static void Main() < Person[] peopleArray = new Person[3] < new Person("John", "Smith"), new Person("Jim", "Johnson"), new Person("Sue", "Rabon"), >; People peopleList = new People(peopleArray); foreach (Person p in peopleList) Console.WriteLine(p.firstName + " " + p.lastName); > > /* This code produces output similar to the following: * * John Smith * Jim Johnson * Sue Rabon * */ 
Imports System.Collections ' Simple business object. Public Class Person Public Sub New(ByVal fName As String, ByVal lName As String) Me.firstName = fName Me.lastName = lName End Sub Public firstName As String Public lastName As String End Class ' Collection of Person objects, which implements IEnumerable so that ' it can be used with ForEach syntax. Public Class People Implements IEnumerable Private _people() As Person Public Sub New(ByVal pArray() As Person) _people = New Person(pArray.Length - 1) <> Dim i As Integer For i = 0 To pArray.Length - 1 _people(i) = pArray(i) Next i End Sub ' Implementation of GetEnumerator. Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator Return New PeopleEnum(_people) End Function End Class ' When you implement IEnumerable, you must also implement IEnumerator. Public Class PeopleEnum Implements IEnumerator Public _people() As Person ' Enumerators are positioned before the first element ' until the first MoveNext() call. Dim position As Integer = -1 Public Sub New(ByVal list() As Person) _people = list End Sub Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext position = position + 1 Return (position < _people.Length) End Function Public Sub Reset() Implements IEnumerator.Reset position = -1 End Sub Public ReadOnly Property Current() As Object Implements IEnumerator.Current Get Try Return _people(position) Catch ex As IndexOutOfRangeException Throw New InvalidOperationException() End Try End Get End Property End Class Class App Shared Sub Main() Dim peopleArray() As Person = < _ New Person("John", "Smith"), _ New Person("Jim", "Johnson"), _ New Person("Sue", "Rabon")>Dim peopleList As New People(peopleArray) Dim p As Person For Each p In peopleList Console.WriteLine(p.firstName + " " + p.lastName) Next End Sub End Class ' This code produces output similar to the following: ' ' John Smith ' Jim Johnson ' Sue Rabon 

Комментарии

Инструкция foreach языка C# ( For Each в Visual Basic) позволяет скрыть сложный механизм перечислителей. Поэтому рекомендуется вместо непосредственного использования перечислителя применять ключевое слово foreach .

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

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

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

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

Если в коллекцию вносятся изменения, такие как добавление, изменение или удаление элементов, поведение перечислителя не определено.

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

Применяется к

См. также раздел

  • IEnumerator
  • Итераторы (C#)
  • Итераторы (Visual Basic)

IEnumerable.Get Enumerator Метод

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

Возвращает перечислитель, выполняющий перебор элементов в коллекции.

public: System::Collections::Generic::IEnumerator ^ GetEnumerator();
public System.Collections.Generic.IEnumerator GetEnumerator ();
public System.Collections.Generic.IEnumerator GetEnumerator ();
abstract member GetEnumerator : unit -> System.Collections.Generic.IEnumerator
Public Function GetEnumerator () As IEnumerator(Of Out T)
Public Function GetEnumerator () As IEnumerator(Of T)
Возвращаемое значение

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

Примеры

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 

Комментарии

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

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

Current возвращает тот же объект до тех пор, пока не MoveNext будет вызван снова, как MoveNext присваивает Current следующему элементу.

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

Если в коллекцию вносятся изменения, такие как добавление, изменение или удаление элементов, поведение перечислителя не определено.

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

Реализации коллекций по умолчанию System.Collections.Generic в пространстве имен не синхронизируются.

IEnumerable , C#

Компилятор упорно выдает ошибку : . не реализуется член интерфейса «System.Collections.IEnumerable.GetEnumerator()» Не совсем понятно , как можно реализовать метод IEnumerable.GetEnumerator() ведь IEnumerable обобщенный интерфейс и нужно указывать тип коллекции(что я и делал). Заранее спасибо.

Отслеживать
задан 20 янв 2012 в 21:10
370 1 1 золотой знак 12 12 серебряных знаков 33 33 бронзовых знака

Понимаете в том то и дело : я пробовал реализовать 2-ой метод ,но компилятор сразу красным подчеркивает слово IEnumerator , если не указывать тип T . Наверное нагляднее будет конкретный кусок кода показать : public class garage : IEnumerable < private Sport_car[] car_list; IEnumeratorIEnumerable.GetEnumerator() < return (IEnumerator)car_list.GetEnumerator(); > IEnumerator IEnumerable.GetEnumerator() < return car_list.GetEnumerator(); >..

20 янв 2012 в 23:09

Пишет , что использование универсального типа «System.Collection.Generic.IEnumerator» требует аргумента типа «1» То есть нельзя даже просто написать IEnumerator почему-то

20 янв 2012 в 23:16

Исправьте IEnumerator на System.Collections.IEnumerator , либо добавьте соответствующий using . Inumerator и IEnumerator это разные типы, они находятся в разных пространствах имён, однако второй интерфейс требует реализации первого.

20 янв 2012 в 23:59

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Интерфейс, к-й вы реализуете в свою очередь реализует не типизированный IEnumerable:

public interface IEnumerable : IEnumerable < IEnumeratorGetEnumerator(); > public interface IEnumerator : IDisposable, IEnumerator < T Current < get; >> public interface IEnumerator < object Current < get; >bool MoveNext(); void Reset(); > 

Следовательно вам необходимо реализовать и его «внутренности», а именно еще один метод GetEnumerator() и IEnumerator:

using System.Collections; using System.Collections.Generic; public class GarageEnumerator : IEnumerator  < private readonly IEnumerator enumerator; public GarageEnumerator(IEnumerator enumerator) < this.enumerator = enumerator; >public void Dispose() < >public bool MoveNext() < return enumerator.MoveNext(); >public void Reset() < enumerator.Reset(); >public T Current < get < return (T)enumerator.Current; >> object IEnumerator.Current < get < return Current; >> > public class Garage : IEnumerable  < private readonly T[] carList; //initialization ! public IEnumeratorGetEnumerator() < return new GarageEnumerator(carList.GetEnumerator()); > IEnumerator IEnumerable.GetEnumerator() < return GetEnumerator(); >> 

А вообще пользуйтесь встроенными средствами в VS — она может сделать ето за вас.

UPDATE: начиная с 3.5 фреймворка можно подключить:

using System.Linq; 
public class Garage : IEnumerable  < private readonly T[] carList = new T[10]; //initialization ! public IEnumeratorGetEnumerator() < return carList.Cast().GetEnumerator(); > IEnumerator IEnumerable.GetEnumerator() < return GetEnumerator(); >> 

Getenumerator c как реализовать

Итератор по сути представляет блок кода, который использует оператор yield для перебора набора значений. Данный блок кода может представлять тело метода, оператора или блок get в свойствах.

Итератор использует две специальных инструкции:

  • yield return : определяет возвращаемый элемент
  • yield break : указывает, что последовательность больше не имеет элементов

Рассмотрим небольшой пример:

Numbers numbers = new Numbers(); foreach (int n in numbers) < Console.WriteLine(n); >class Numbers < public IEnumeratorGetEnumerator() < for (int i = 0; i < 6; i++) < yield return i * i; >> >

В классе Numbers метод GetEnumerator() фактически представляет итератор. С помощью оператора yield return возвращается некоторое значение (в данном случае квадрат числа).

В программе с помощью цикла foreach мы можем перебрать объект Numbers как обычную коллекцию. При получении каждого элемента в цикле foreach будет срабатывать оператор yield return, который будет возвращать один элемент и запоминать текущую позицию.

Благодаря итераторам мы можем пойти дальше и легко реализовать перебор числа в цикле foreach:

foreach(var n in 5) Console.WriteLine(n); foreach (var n in -5) Console.WriteLine(n); static class Int32Extension < public static IEnumeratorGetEnumerator(this int number) < int k = (number >0)? number: 0; for (int i = number - k; i >

В данном случае итератор реализован как метод расширения для типа int или System.Int32. В методе итератора фактически возвращаем все целочисленные значения от 0 до текущего числа. Консольный вывод:

0 1 2 3 4 5 -5 -4 -3 -2 -1 0

Другой пример: пусть у нас есть коллекция Company, которая представляет компанию и которая хранит в массиве personnel штат сотрудников — объектов Person. Используем оператор yield для перебора этой коллекции:

class Person < public string Name < get; >public Person(string name) =>Name = name; > class Company < Person[] personnel; public Company(Person[] personnel) =>this.personnel = personnel; public int Length => personnel.Length; public IEnumerator GetEnumerator() < for (int i = 0; i < personnel.Length; i++) < yield return personnel[i]; >> >

Метод GetEnumerator() представляет итератор. И когда мы будем осуществлять перебор в объекте Company в цикле foreach, то будет идти обращение к вызову yield return personnel[i]; . При обращении к оператору yield return будет сохраняться текущее местоположение. И когда метод foreach перейдет к следующей итерации для получения нового объекта, итератор начнет выполнения с этого местоположения.

Ну и в основной программе в цикле foreach выполняется собственно перебор, благодаря реализации итератора:

var people = new Person[] < new Person("Tom"), new Person("Bob"), new Person("Sam") >; var microsoft = new Company(people); foreach(Person employee in microsoft)

Хотя при реализации итератора в методе GetEnumerator() применялся перебор массива в цикле for, но это необязательно делать. Мы можем просто определить несколько вызовов оператора yield return :

public IEnumerator GetEnumerator()

В этом случае при каждом вызове оператора yield return итератор также будет запоминать текущее местоположение и при последующих вызовах начинать с него.

Именованный итератор

Выше для создания итератора мы использовали метод GetEnumerator . Но оператор yield можно использовать внутри любого метода, только такой метод должен возвращать объект интерфейса IEnumerable . Подобные методы еще называют именованными итераторами .

Создадим такой именованный итератор в классе Company и используем его:

class Person < public string Name < get; >public Person(string name) =>Name = name; > class Company < Person[] personnel; public Company(Person[] personnel) =>this.personnel = personnel; public int Length => personnel.Length; public IEnumerable GetPersonnel(int max) < for (int i = 0; i < max; i++) < if (i == personnel.Length) < yield break; >else < yield return personnel[i]; >> > >

Определенный здесь итератор — метод IEnumerable GetPersonnel(int max) в качестве параметра принимает количество выводимых объектов. В процессе работы программы может сложиться, что его значение будет больше, чем длина массива personnel. И чтобы не произошло ошибки, используется оператор yield break . Этот оператор прерывает выполнение итератора.

var people = new Person[] < new Person("Tom"), new Person("Bob"), new Person("Sam") >; var microsoft = new Company(people); foreach(Person employee in microsoft.GetPersonnel(5))

Вызов microsoft.GetPersonnel(5) будет возвращать набор из не более чем 5 объектов Person. Но так как у нас всего три таких объекта, то в методе GetPersonnel после трех операций сработает оператор yield break .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *