Оператор yield
class UserCollection
{
public static IEnumerable Power()
{
yield return "Hello world!";
}
}
Оператор yield из .Net Reflector - перенесенный в рялии C# и Visual Studio
class UserCollection
{
public static IEnumerable Power()
{
return new ClassPower(-2);
}
private sealed class ClassPower : IEnumerable
Ответ
Обратите внимание на функцию IEnumerable
Данный объект реализует как IEnumerable, так и IEnumerator. Состояние -2 означает, что объект «свежий», только что вернулся из вызова функции Power, и его энумерация ещё не начиналась. В этой ситуации, когда тут же вызывается GetEnumerator в том же потоке (а это происходит в подавляющем большинстве случаев: например, при вызове foreach (var x in Power())), то из соображений эффективности можно вернуть этот же объект (т. к. он служит и энумератором тоже).
Но если энумерация уже прошла, внутреннее состояние объекта может быть испорчено, для этого случая возвращают новый объект. Точно так же если энумерация производится в другом потоке, то чтобы избежать необходимости синхронизации, лучше создать новый объект.
Остановимся специально на последнем пункте. Если вдруг IEnumerable уйдёт в другой поток, и оттуда буден вызван метод GetEnumerator(), и одновременно в основном потоке будет тоже вызван этот же метод, то без проверки thread id может случиться так, что оба потока пройдут одновременно проверку this.state == -2, и получат один и тот же объект! В результате при энумерации они будут мешать друг другу. В этом случае мы даём «выиграть» тому же потоку, который создал объект.
Таким образом, это специальная оптимизация для обычного случая (выделение только одного объекта); «необычные» случаи использования проходят более сложным путём.
Подробнее по теме: Jon Skeet, C# in Depth. Iterator block implementation details: auto-generated state machines
Комментариев нет:
Отправить комментарий