F # выход!оператор - Реализация и возможные эквиваленты C # - PullRequest
15 голосов
/ 17 августа 2010

Я сейчас изучаю F #, и мне действительно нравится оператор yield! (yield-bang).Конечно, не только для его имени, но и для того, что он делает.

Оператор yield! в основном позволяет вам получить все элементы последовательности из выражения последовательности.Это полезно для составления перечислителей.Поскольку я регулярно сталкиваюсь с большими и сложными перечислителями, меня интересуют стратегии, которые мы можем использовать, чтобы разбить их и составить из более простых перечислителей.

К сожалению, оператор yield! недоступен в C #.Насколько я понимаю, это похоже на foreach (var x in source) yield x;, но книга, которую я читаю ( Реальный мир Петричека F # - Мэннинг ), предполагает, что он имеет лучшую производительность ...

  • Так что же здесь делает компилятор F #?(Да, я тоже могу посмотреть на него с помощью Reflector, но мне бы хотелось иметь более подробное описание механизма).

Чтобы достичь аналогичной конструкции в C #, я исследовал несколько способов, но ни один из них не является таким кратким, как оператор yield!, и я также не уверен в их сложности.Может ли кто-нибудь предоставить ввод, если мои цифры BigO верны?

  • Разложить перечислитель на несколько частных перечислителей, а затем вывести каждый элемент из открытого перечислителя:

    foreach (var x in part1()) yield x
    foreach (var x in part2()) yield x
    

    Thisэффективно приведет к «двойной доходности» на каждом элементе.Это O (2n) тогда?(или, возможно, хуже?) В любом случае, использование этого подхода мешает мне использовать yield break; из любой из моих частей.

  • Разложить перечислитель на несколько частных перечислителей, а затем объединить все частные перечислители изpublic enumerator:

    return part1().Concat(part2())
    

    Полагаю, это ничем не отличается от вышеупомянутого решения, поскольку Concat() реализован так, как я обрисовал выше.

Есть еще варианты?

Ответы [ 3 ]

7 голосов
/ 17 августа 2010

В текущей версии C # я не думаю, что у вас есть другие варианты, кроме foreach... yield return и Concat.Я согласен, что было бы неплохо иметь оператор yield! в C #, это сделало бы некоторые конструкции намного более элегантными, но я сомневаюсь, что эта функция когда-нибудь попадет в список «must-have», так как мы легко можем обойтись без него.

Возможно, вас заинтересует исследовательская статья MS , в которой представлена ​​новая конструкция yield foreach:

IEnumerable<XmlNode> Traverse(XmlNode n)
{
    yield return n;
    foreach (XmlNode c in n.ChildNodes)
        yield foreach Traverse(c);
}

Относительно вашего вопроса о сложности: в обоих случаяхэто O (n) . O (2n) не используется, поскольку он обозначает ту же сложность, что и O (n) (линейный).Я не думаю, что вы можете добиться большего успеха, чем нынешние функции C # ...

6 голосов
/ 17 августа 2010

Относительно того, как компилятор переводит операцию yield!, статья , цитируемая Томасом Левеском в его ответе, иллюстрирует один метод реализации в разделе 4.3 (в частности, их примерные охватывающие цифры 7-9 иллюстрируют общая стратегия). Я не думаю, что есть какой-то хороший способ сделать это из блока итераторов в C # - поскольку я понимаю ваши предложенные решения, они оба могут привести к квадратичному поведению при рекурсивном использовании. Вы всегда можете вручную создать подкласс NestedEnumerable<T>, чтобы добиться повышения производительности, но это будет довольно уродливо по сравнению с использованием обычного блока итератора.

3 голосов
/ 17 августа 2010

Нет прямого аналога yield! в C #.В настоящее время вы застряли с комбинацией foreach и yield return.

. Однако IIRC, LINQ предлагает нечто похожее, а именно оператор запроса SelectMany, который переводит в C # несколько предложений from .. in ....

(Надеюсь, я не смешиваю два разных понятия, но IIRC, оба yield! и SelectMany, по сути, являются "сглаживающими" проекциями, т.е. иерархия объектов "сглаживается"в список.)

...