| Comments: |
Мой коллега, в отличие от меня, дал почти правильный ответ - 1,1,1,1,1... :)
Переведи, пожалуйста, шутку для людей, которые на третьем шарпе не писали. Что, не 1-2-3 получим? А что? И почему? Не хочется искать компилятор только для того, чтобы узнать; а не знать после того, как прочитал, уже досадно.
>Что, не 1-2-3 получим? А что?
В майкрософтовской реализации FCL получим бесконечную последовательность default(int)'ов, то бишь нулей.
>а не знать после того, как прочитал, уже досадно.
Согласен. Чуть позже объясню, в чём дело.
>Переведи, пожалуйста, шутку для людей, которые на третьем шарпе не писали.
Я, в свою очередь, на втором Шарпе не писал. Так что не уверен, что из приведённого компилятору второго Шарпа будет непонятно. Наверное, type inference, in-place initialization и anonymous type. В принципе, программу можно перевести и на C# 2.0 без потери этого забавного эффекта, хотя грабли будут лучше просматриваться. После работы напишу, ок?
Остальные могут пока предлагать свои варианты. (Чур, на источник этюда ссылку не давать, я сам позже выложу.)
![[User Picture]](http://l-userpic.livejournal.com/43376717/9763421) | From: bik_top 2009-04-14 08:57 pm (UTC)
Решение, 1/3 | (Link)
|
В переводе на C# 2.0:
using System;
using System.Collections.Generic;
class EntryPoint
{
private class Anonymous
{
private List<int>.Enumerator y;
public List<int>.Enumerator Y { get { return y; } set { y = value; } }
}
static void Main()
{
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
Anonymous x = new Anonymous();
x.Y = list.GetEnumerator();
...
Продолжение следует...
![[User Picture]](http://l-userpic.livejournal.com/43376717/9763421) | From: bik_top 2009-04-14 09:06 pm (UTC)
Решение, 2/3 | (Link)
|
Продолжение комментария 185789.
...
while (x.Y.MoveNext())
Console.WriteLine(x.Y.Current);
}
}
Это более многословно, зато начинают просматриваться подводные грабли. А именно, List<int>.Enumerator. Тут стоит сделать лирическое отступление.
Value-типы в .NET задуманы быть простыми, как пять копеек. И тем не менее, их проектирование весьма нетривиально, обложено кучей идиом и внеязыковых ограничений. Один из принципов можно сформулировать в хрестоматийной перефразировке:
1. Не делай value-типы изменяемыми.
2. (Только для профессионалов.) Никогда не делай value-типы изменяемыми.
И наоборот, если нужен изменяемый тип, то не делай его структурой. Обсуждение этого гайдлайна можно найти во многих источниках.
Так вот, проектируя List<T>, разработчики FCL этот принцип нарушили, List<T></span>.Enumerator — это mutable struct. Они хотели соптимизировать перебор коллекции foreach'ем, и они его таки соптимизировали. (1. Не оптимизируй. 2. (Только для профессионалов.) Ну вы поняли, да?)
<offtop>
Кстати, как это поможет улучшить перебор foreach'ем? Оказывается, для работы foreach'а реализация интерфейсов IEnumerable/IEnumerator не является необходимой (см., например, MSDN). Достаточно, чтобы классы просто предоставляли нужные члены (немного смахивает на шаблоны в C++). В List<T> есть метод GetEnumerator(), возвращающий перечислитель подходящего value-типа. (Но в то же время, List<T> реализует интерфейсы IEnumerable и IEnumerable<T>, как же методы с разными возвращаемыми типами уживаются вместе? Это возможно благодаря т. н. explicit interface implementation.) Это позволяет при переборе массива не плодить боксингов, аллокаций на куче, лишних индирекшнов — profit.
</offtop>
Всё работает прозрачно для пользователя, и в густой траве он не видит противопехотных граблей. А цимес в том, что методы перечислителя MoveNext() и Current вызываются для разных экземпляров типа List<int>.Enumerator. Потому что происходит скрытое копирование объекта перечислителя при обращении к свойству через x.Y.
Продолжение следует...
![[User Picture]](http://l-userpic.livejournal.com/43376717/9763421) | From: bik_top 2009-04-14 09:12 pm (UTC)
Решение, 3/3 | (Link)
|
Продолжение комментария 186045.
Если бы Y был полем, а не свойством, то всё было бы ок:
private class Anonymous
{
public IEnumerator<int> Y;
}
Если бы y был ссылкой, а не значением, то опять же всё было бы ок:
private class Anonymous
{
private IEnumerator<int> y;
public IEnumerator<int> Y { get { return y; } set { y = value; } }
}
Но комбинация этих ньюансов приводит к тому, что MoveNext() вызывается для копии нашего перечислителя, он никуда не сдвигается, и обращение к его свойству Current приводит к «enumeration has either not started or has already finished». Разработчики FCL реализовали перечислитель так, что в данном невалидном состоянии он возвращает default(T). Могли б исключение кинуть, но оптимизация ж...
Понятно, почему на C# 3.0 задача смотрится интересней, чем на C# 2.0. Во-первых, type inference скрывает от читателя конкретный тип-структуру List<int>.Enumerator, провоцирует делать правдоподобное заключение, что GetEnumerator() вернёт заурядный IEnumerator<T>. Во-вторых, не представляется очевидным, что в генерируемом анонимном типе Y — это свойство, а не поле. В-третьих, создание и инициализация списка на лету заставляет напрягаться насчёт времени жизни коллекции.
Этот этюд предложил Владимир Решетников (RSDN-user nikov), то в виду лежания сайта ссылку дать не могу.
Теперь можно комментировать.
Имелся в виду конкретный тип. Следует читать так:
Если бы Y был полем, а не свойством, то всё было бы ок:
private class Anonymous
{
public List<int>.Enumerator Y;
}
Message = "Enumeration has either not started or has already finished."
Нет.это в дебаге подсмотрел, а так нули за-loop-ились | |