Есть ли реальная причина для этой ошибки в данном коде, или просто это может пойти не так при обычном использовании, когда потребуется ссылка на шаг интератора (что неверно в данном случае)?
IEnumerable<string> EnumerateStatic()
{
foreach (int i in dict.Values)
{
ref var p = ref props[i]; //< CS8176: Iterators cannot have by-reference locals
int next = p.next;
yield return p.name;
while (next >= 0)
{
p = ref props[next];
next = p.next;
yield return p.name;
}
}
}
struct Prop
{
public string name;
public int next;
// some more fields like Func<...> read, Action<..> write, int kind
}
Prop[] props;
Dictionary<string, int> dict;
dict
– карта индекса имен, регистронезависимаяProp.next
– указывает на следующий узел для повторения (-1 в качестве признака конца; поскольку dict
нечувствительна к регистру, а это связано -list был добавлен для разрешения конфликтов путем поиска с учетом регистра с откатом к первому).
Я вижу сейчас два варианта:
- Реализовать пользовательский итератор/перечислитель, mscs/Roslyn сейчас недостаточно хорош, чтобы хорошо видеть и выполнять свою работу. (Здесь нет виноватых, я понимаю, не такая уж и важная функция.)
- Отбросьте оптимизацию и просто проиндексируйте ее дважды (один раз для
name
и второй раз дляnext
). Возможно, компилятор все равно это поймет и создаст оптимальный машинный код. (Я создаю скриптовый движок для Unity, это действительно критично для производительности. Может быть, он просто проверяет границы один раз и в следующий раз использует доступ, подобный ссылке/указателю, бесплатно.)
И, может быть, 3. (2b, 2+1/2) Просто скопируйте структуру (32B на x64, три ссылки на объекты и два целых числа, но может расти, будущее не видно). Вероятно, это не очень хорошее решение (либо я забочусь и пишу итератор, либо он так же хорош, как 2.)
Что я понимаю:
ref var p
не может жить после yield return
, потому что компилятор строит итератор - конечный автомат, ref
нельзя передать следующему IEnumerator.MoveNext()
. Но это не тот случай.
Чего я не понимаю:
Почему применяется такое правило, вместо того, чтобы пытаться фактически сгенерировать итератор/перечислитель, чтобы увидеть, нужно ли такому ref var
пересекать границу (что здесь не нужно). Или любой другой способ выполнить работу, которая выглядит выполнимой (я понимаю, что то, что я себе представляю, сложнее реализовать, и ожидаю, что ответ будет таким: у ребят из Рослина есть дела поважнее. Опять же, без обид, вполне верный ответ.)
Ожидаемые ответы:
- Да, может быть в будущем/не стоит (создайте Issue - сделаю, если сочтете, что оно того стоит).
- Есть лучший способ (пожалуйста, поделитесь, мне нужно решение).
Если вам нужен дополнительный контекст, он для этого проекта: https://github.com/evandisoft/RedOnion/tree/master/RedOnion.ROS/Descriptors/Reflect (Reflected.cs и Members.cs)
Воспроизводимый пример:
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
class Test
{
struct Prop
{
public string name;
public int next;
}
Prop[] props;
Dictionary<string, int> dict;
public IEnumerable<string> Enumerate()
{
foreach (int i in dict.Values)
{
ref var p = ref props[i]; //< CS8176: Iterators cannot have by-reference locals
int next = p.next;
yield return p.name;
while (next >= 0)
{
p = ref props[next];
next = p.next;
yield return p.name;
}
}
}
}
static void Main(string[] args)
{
}
}
}