Многоуровневый механизм доходности в C#

У меня есть вопрос по Code Review SE, где я пытаюсь понять, как реорганизовать мой метод перечислителя, чтобы он не был таким уж полным беспорядком.

Я думаю, что отчасти проблема в том, что я не могу понять, как вызывать другие методы изнутри и получать результаты без необходимости заключать этот внутренний вызов в foreach и выдавать элементы один за другим.

Есть ли способ сделать какой-то мультивыход на С#, чтобы это работало?

    private IEnumerable<int> Outer()
    {
        yield return 0;
        yield return Inner(); // Some sort of multi-yield
        yield return 5;
    }
    
    private IEnumerable<int> Inner()
    {
        yield return 1;
        yield return 2;
        yield return 3;
        yield return 4;
    }

Я знаю, что могу это сделать, но это не то, что я ищу, поскольку к тому времени, как я проделаю это несколько раз, моя отрефакторенная версия будет не намного лучше:

    private IEnumerable<int> Outer()
    {
        yield return 0;
        foreach (int i in Inner())
        {
            yield return i;
        }
        yield return 5;
    }

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

То, что вы делаете, в значительной степени лучший способ. Однако в некоторых случаях вы можете изучить методы LINQ Union или Concat.

Alejandro 06.05.2024 21:51
yield можно производить только один предмет за раз, нет «кратного выхода» или чего-то в этом роде. Либо используйте подход foreach, либо, например. создайте посредника List<int>, потому что он поддерживает AddRange().
Peter B 06.05.2024 21:51

То, что вы ищете, невозможно сделать. Элементы Inner невозможно узнать, не перечислив их. Вы можете избежать yield и сделать что-то вроде return Inner().Prepend(0).Append(5) или return Enumerable.Empty<int>().Append(0).Concat(Inner()).Append(5);, но это то же самое, за исключением того, что это делают методы LINQ, и здесь задействовано больше перечисляемых чисел.

madreflection 06.05.2024 21:52

@PeterB Хотя это абсолютно правильно, размещение временного списка материализует всю последовательность, в первую очередь сводя на нет цель yield return.

Alejandro 06.05.2024 21:54

@madreflection — могу добавить запрос на добавление функции, потому что думаю, что это может быть полезно. Возможность иметь внутренний перечислитель кажется чем-то, что не должно быть полностью реализовано как концепция.

ScottishTapWater 06.05.2024 21:55

Он все равно будет делать то же самое, но теперь скрыто от разработчика. «Отдачи от вложенных средств» здесь нет, и я уверен, что вы не первый, кто об этом думает. Нет ничего плохого в цикле, который нужно повторять Inner().

madreflection 06.05.2024 21:56

Да, конечно, но в наши дни в язык добавляется множество вещей, по сути, являющихся синтаксическим сахаром. Возможно, вы правы, что для того, чтобы его развивать, не будет достаточной ценности.

ScottishTapWater 06.05.2024 21:57

Где-то в официальном репозитории GitHub есть предложение ввести какой-то yield return all, который должен делать именно то, что вы просите. Но это все равно предложение :(

CSharpie 06.05.2024 21:57

Я думаю, это подпадает под это? github.com/dotnet/csharpang/discussions/378

ScottishTapWater 06.05.2024 21:58
I'm struggling to work out how to refactor my enumerator method to be less of a complete mess — делайте все, чтобы ваш код было легче читать и/или поддерживать. Например, метод, похожий на Inner(), может иметь смысл, если имя собственное указывает на его назначение и будет использоваться повторно где-нибудь еще.
Rui Jarimba 06.05.2024 22:00

@RuiJarimba - я на самом деле пошел и просто реорганизовал его, чтобы не использовать перечисляемое, потому что в моем контексте это ненужно. Однако у меня остался этот вопрос

ScottishTapWater 06.05.2024 22:02

Я очень сомневаюсь, что это истинное решение, поскольку ваш пример кода, вероятно, упрощен, но для чего-то вроде вашего примера кода, для последовательного создания чисел, есть Enumerable.Range(start, count), который затем можно сбросить в выбранную вами коллекцию, поскольку он возвращает IEnumerable

Narish 06.05.2024 22:55

Честно говоря, вложение перечислителей звучит как кошмар. Если я это сделаю Outer().Take(3), мне следует ожидать [0,1,2] или [0, [1,2,3,4], 5]? Ситуация усложняется тем, что новый/неопытный разработчик, активно использующий var, может даже не распознать, что это не ожидаемый плоский или неровный массив. Я даже не хочу, чтобы мне пришлось столкнуться с такой ситуацией. Я настоятельно не советую открывать такой запрос функции...

Narish 06.05.2024 22:56

@Narish - [0, 1, 2] — это то, к чему я стремлюсь. Пример в этом вопросе академический. Если вы посмотрите на связанный вопрос в Code Review, вы увидите фактический код.

ScottishTapWater 06.05.2024 23:06

@ScottishTapWater Верно, поэтому нам придется значительно переопределить LINQ, чтобы иметь возможность перейти от его поведения статус-кво к возможности LINQ анализировать, когда ему нужно обрабатывать неровный массив как плоский массив... Слишком много быть в черном ящике

Narish 06.05.2024 23:15

Глядя на ваш код, я вообще не являюсь экспертом в рефлексии, но не вижу ничего плохого в том, что там происходит цикл foreach. Сложность здесь также является спорным вопросом... нам нужно повторить

Narish 06.05.2024 23:15

@Narish - Ну нет, ты бы использовал другое ключевое слово, не так ли?

ScottishTapWater 07.05.2024 00:29

Возможно, вам нужно написать преобразование исходного кода, которое выполняется во время компиляции, чтобы вы могли написать что-то вроде //yieldmany return Inner() в своем коде, а ваш генератор подхватит это и на лету преобразует в foreach(var v in Inner()) yield return v.

flackoverstow 07.05.2024 07:59

@flackoverstow — это один из немногих случаев, когда мне действительно хотелось бы, чтобы в C# были правильные макросы

ScottishTapWater 07.05.2024 20:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
19
139
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не сможете добиться большего, если сохраните некоторые утверждения yield return в методе верхнего уровня. Но если все операторы yield return находятся в методах листа, вы можете использовать IEnumerable.Concat:

    private IEnumerable<int> Outer()
    {
        return Inner1().Concat(Inner2()).Concat(Inner3());
    }

    private IEnumerable<int> Inner1()
    {
        yield return 0;
    }
    
    private IEnumerable<int> Inner2()
    {
        yield return 1;
        yield return 2;
        yield return 3;
        yield return 4;
    }

    private IEnumerable<int> Inner3()
    {
        yield return 5;
    }

Не приведет ли это к немедленному перечислению всего этого, тем самым сводя на нет суть?

ScottishTapWater 07.05.2024 00:29

@ScottishTapWater Справедливый вопрос, но хорошая новость заключается в том, что методы Linq, такие как Concat() в IEnumerable, хорошо разработаны в этом отношении. Материализуют перечислимое такие методы, как ToList() или ToArray().

Pac0 07.05.2024 00:58

Рабочий пример: onlinegdb.com/ACtXOoC9Y

Pac0 07.05.2024 01:00

@ScottishTapWater на самом деле не о чем беспокоиться, потому что они уступают себя и действительно «делают что-нибудь» только тогда, когда результат, например, concat перечисляется. Ознакомьтесь с исходным кодом по адресу referencesource.microsoft.com/#System.Core/System/Linq/… (или на github для ядра .net, но вряд ли он будет существенно отличаться)

flackoverstow 07.05.2024 07:50

Другие вопросы по теме