У меня есть вопрос по 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. Однако этот вопрос по-прежнему актуален, если я хочу представить что-то подобное как перечисляемое из библиотеки или что-то в этом роде. Это просто не лучший вариант для решения связанной проблемы. Однако я оставлю ссылку на это для дополнительного контекста.
yield можно производить только один предмет за раз, нет «кратного выхода» или чего-то в этом роде. Либо используйте подход foreach, либо, например. создайте посредника List<int>, потому что он поддерживает AddRange().
То, что вы ищете, невозможно сделать. Элементы Inner невозможно узнать, не перечислив их. Вы можете избежать yield и сделать что-то вроде return Inner().Prepend(0).Append(5) или return Enumerable.Empty<int>().Append(0).Concat(Inner()).Append(5);, но это то же самое, за исключением того, что это делают методы LINQ, и здесь задействовано больше перечисляемых чисел.
@PeterB Хотя это абсолютно правильно, размещение временного списка материализует всю последовательность, в первую очередь сводя на нет цель yield return.
@madreflection — могу добавить запрос на добавление функции, потому что думаю, что это может быть полезно. Возможность иметь внутренний перечислитель кажется чем-то, что не должно быть полностью реализовано как концепция.
Он все равно будет делать то же самое, но теперь скрыто от разработчика. «Отдачи от вложенных средств» здесь нет, и я уверен, что вы не первый, кто об этом думает. Нет ничего плохого в цикле, который нужно повторять Inner().
Да, конечно, но в наши дни в язык добавляется множество вещей, по сути, являющихся синтаксическим сахаром. Возможно, вы правы, что для того, чтобы его развивать, не будет достаточной ценности.
Где-то в официальном репозитории GitHub есть предложение ввести какой-то yield return all, который должен делать именно то, что вы просите. Но это все равно предложение :(
Я думаю, это подпадает под это? github.com/dotnet/csharpang/discussions/378
I'm struggling to work out how to refactor my enumerator method to be less of a complete mess — делайте все, чтобы ваш код было легче читать и/или поддерживать. Например, метод, похожий на Inner(), может иметь смысл, если имя собственное указывает на его назначение и будет использоваться повторно где-нибудь еще.
@RuiJarimba - я на самом деле пошел и просто реорганизовал его, чтобы не использовать перечисляемое, потому что в моем контексте это ненужно. Однако у меня остался этот вопрос
Я очень сомневаюсь, что это истинное решение, поскольку ваш пример кода, вероятно, упрощен, но для чего-то вроде вашего примера кода, для последовательного создания чисел, есть Enumerable.Range(start, count), который затем можно сбросить в выбранную вами коллекцию, поскольку он возвращает IEnumerable
Честно говоря, вложение перечислителей звучит как кошмар. Если я это сделаю Outer().Take(3), мне следует ожидать [0,1,2] или [0, [1,2,3,4], 5]? Ситуация усложняется тем, что новый/неопытный разработчик, активно использующий var, может даже не распознать, что это не ожидаемый плоский или неровный массив. Я даже не хочу, чтобы мне пришлось столкнуться с такой ситуацией. Я настоятельно не советую открывать такой запрос функции...
@Narish - [0, 1, 2] — это то, к чему я стремлюсь. Пример в этом вопросе академический. Если вы посмотрите на связанный вопрос в Code Review, вы увидите фактический код.
@ScottishTapWater Верно, поэтому нам придется значительно переопределить LINQ, чтобы иметь возможность перейти от его поведения статус-кво к возможности LINQ анализировать, когда ему нужно обрабатывать неровный массив как плоский массив... Слишком много быть в черном ящике
Глядя на ваш код, я вообще не являюсь экспертом в рефлексии, но не вижу ничего плохого в том, что там происходит цикл foreach. Сложность здесь также является спорным вопросом... нам нужно повторить
@Narish - Ну нет, ты бы использовал другое ключевое слово, не так ли?
Возможно, вам нужно написать преобразование исходного кода, которое выполняется во время компиляции, чтобы вы могли написать что-то вроде //yieldmany return Inner() в своем коде, а ваш генератор подхватит это и на лету преобразует в foreach(var v in Inner()) yield return v.
@flackoverstow — это один из немногих случаев, когда мне действительно хотелось бы, чтобы в C# были правильные макросы





Вы не сможете добиться большего, если сохраните некоторые утверждения 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 Справедливый вопрос, но хорошая новость заключается в том, что методы Linq, такие как Concat() в IEnumerable, хорошо разработаны в этом отношении. Материализуют перечислимое такие методы, как ToList() или ToArray().
Рабочий пример: onlinegdb.com/ACtXOoC9Y
@ScottishTapWater на самом деле не о чем беспокоиться, потому что они уступают себя и действительно «делают что-нибудь» только тогда, когда результат, например, concat перечисляется. Ознакомьтесь с исходным кодом по адресу referencesource.microsoft.com/#System.Core/System/Linq/… (или на github для ядра .net, но вряд ли он будет существенно отличаться)
То, что вы делаете, в значительной степени лучший способ. Однако в некоторых случаях вы можете изучить методы LINQ
UnionилиConcat.