- PVSM.RU - https://www.pvsm.ru -
В этой статье мы коротко пройдемся по особенностям foreach. Первый момент вы скорее всего знаете, второй момент вы скорее всего не знаете.
Предыдущая статья об особенностях C# [1].
На собеседованиях часто спрашивают — «Что необходимо сделать что бы ваш класс работал с foreach?». Ответ на этот вопрос обычно звучит так — «Реализовать IEnumerable». Ответ этот правильный, но не полный. В принципе, этого ответа на собеседовании достаточно и я ни разу не встречал чтобы кто то считал его неправильным. На самом деле, foreach использует «утиную типизацию» [2]. Для того чтобы наш класс работал в foreach достаточно иметь метод GetEnumerator возвращающий нечто имеющее метод MoveNext и свойство Current.
Запоминать эти методы не обязательно, если подсунуть свой неправильный класс в foreach компилятор честно подскажет чего конкретно не хватает в этом классе.
Тестовый foreach:
class Program
{
static void Main(string[] args)
{
var container = new Container();
foreach (var item in container)
{
}
}
}
Неправильный контейнер:
public class Container
{
}
Ошибка компилятора:
foreach statement cannot operate on variables of type 'Container' because 'Container' does not contain a public definition for 'GetEnumerator'
Добавим метод GetEnumerator в контейнер и класс энумератора.
Правильный контейнер:
public class Container
{
public Enumerator GetEnumerator()
{
return new Enumerator();
}
}
Неправильный энумератор:
public class Enumerator
{
}
Ошибка компилятора:
foreach requires that the return type 'Enumerator' of 'Container.GetEnumerator()' must have a suitable public MoveNext method and public Current property
Добавим метод MoveNext и свойство Current в энумератор.
Правильный энумератор:
public class Enumerator
{
public bool MoveNext()
{
return false;
}
public object Current
{
get { return null; }
}
}
Теперь компилятор все устраивает.
Примечание:
Свойство Current может возвращать любой тип, как ref type так и value type. Собственно это и стало причиной использования «утиной типизации», во времена когда не было generics, для избежания ненужных boxing и unboxing.
На собеседовании встречаются вопросы про IDisposable и помимо общих вопросов про ручное управление ресурсами есть вопрос про то, в каких случаях компилятор может автоматически вызывать метод Dispose. Ответ все мы знаем — Dispose вызывается автоматически при использовании оператора using(). Ответ этот правильный, но неполный! Метод Dispose может вызывается в двух случаях, помимо using(), он вызывается в foreach для энумератора, если энумератор реализует IDisposable.
Энумератор с Dispose:
using System;
public class Enumerator : IDisposable
{
public bool MoveNext()
{
return false;
}
public object Current
{
get { return null; }
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
}
Теперь при запуске примера мы увидим строчку «Dispose» в консоли.
Для тех кому интересно, вот код который генерит компилятор для нашего случая:
Container container = new Container();
Enumerator enumerator = container.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var element = enumerator.Current;
// содержимое foreach
}
}
finally
{
IDisposable disposable = enumerator as IDisposable;
if (disposable != null)
disposable.Dispose();
}
Всем спасибо за внимание!
Автор: mynameco
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/tonkosti/53286
Ссылки в тексте:
[1] Предыдущая статья об особенностях C#: http://habrahabr.ru/post/194626/
[2] «утиную типизацию»: http://ru.wikipedia.org/wiki/Утиная_типизация
[3] Источник: http://habrahabr.ru/post/209914/
Нажмите здесь для печати.