Интересные моменты в C#

от автора

В этой статье мы коротко пройдемся по особенностям foreach. Первый момент вы скорее всего знаете, второй момент вы скорее всего не знаете.

Предыдущая статья об особенностях C#.

Первый момент

На собеседованиях часто спрашивают — «Что необходимо сделать что бы ваш класс работал с foreach?». Ответ на этот вопрос обычно звучит так — «Реализовать IEnumerable». Ответ этот правильный, но не полный. В принципе, этого ответа на собеседовании достаточно и я ни разу не встречал чтобы кто то считал его неправильным. На самом деле, foreach использует «утиную типизацию». Для того чтобы наш класс работал в 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(); } 

Всем спасибо за внимание!

ссылка на оригинал статьи http://habrahabr.ru/post/209914/


Комментарии

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

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