Home

Advertisement

All things considered... - А вы говорите Саттер... [entries|archive|friends|userinfo]
Qbit

[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

А вы говорите Саттер... [Apr. 14th, 2009|02:32 am]
Previous Entry Add to Memories Tell a Friend Next Entry
using System;
using System.Collections.Generic;

class EntryPoint
{
  static void Main()
  {
    var x = new { Y = new List<int> { 1, 2, 3 }.GetEnumerator() };

    while (x.Y.MoveNext())
      Console.WriteLine(x.Y.Current);
  }
}
LinkReply

Comments:
From: [info]smalgin
2009-04-14 03:14 am (UTC)

(Link)

Мой коллега, в отличие от меня, дал почти правильный ответ - 1,1,1,1,1... :)
[User Picture]From: [info]plakhov
2009-04-14 05:49 am (UTC)

(Link)

Переведи, пожалуйста, шутку для людей, которые на третьем шарпе не писали. Что, не 1-2-3 получим? А что? И почему? Не хочется искать компилятор только для того, чтобы узнать; а не знать после того, как прочитал, уже досадно.
[User Picture]From: [info]bik_top
2009-04-14 08:28 am (UTC)

(Link)

>Что, не 1-2-3 получим? А что?

В майкрософтовской реализации FCL получим бесконечную последовательность default(int)'ов, то бишь нулей.

>а не знать после того, как прочитал, уже досадно.

Согласен. Чуть позже объясню, в чём дело.

>Переведи, пожалуйста, шутку для людей, которые на третьем шарпе не писали.

Я, в свою очередь, на втором Шарпе не писал. Так что не уверен, что из приведённого компилятору второго Шарпа будет непонятно. Наверное, type inference, in-place initialization и anonymous type. В принципе, программу можно перевести и на C# 2.0 без потери этого забавного эффекта, хотя грабли будут лучше просматриваться. После работы напишу, ок?

Остальные могут пока предлагать свои варианты. (Чур, на источник этюда ссылку не давать, я сам позже выложу.)
[User Picture]From: [info]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();
    ...

Продолжение следует...

(Replies frozen) (Parent) (Thread)
[User Picture]From: [info]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.

Продолжение следует...

(Replies frozen) (Parent) (Thread)
[User Picture]From: [info]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), то в виду лежания сайта ссылку дать не могу.

Теперь можно комментировать.

[User Picture]From: [info]bik_top
2009-04-14 09:56 pm (UTC)

Erratum

(Link)

Имелся в виду конкретный тип. Следует читать так:

Если бы Y был полем, а не свойством, то всё было бы ок:
private class Anonymous
{
  public List<int>.Enumerator Y;
}

[User Picture]From: [info]mes_ser
2009-04-14 06:23 am (UTC)

(Link)

Message = "Enumeration has either not started or has already finished."
[User Picture]From: [info]bik_top
2009-04-14 06:47 am (UTC)

(Link)

Это на Моно?
[User Picture]From: [info]mes_ser
2009-04-14 08:20 am (UTC)

(Link)

Нет.это в дебаге подсмотрел, а так нули за-loop-ились

Advertisement