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

в 6:05, , рубрики: особенности, тонкости, факты, метки: , ,

В этой статье мы коротко пройдемся по особенностям 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();
}

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

Автор: mynameco

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js