Is there real reason for this error in given code, or just It could go wrong in general usage where the reference would be needed across interator steps (which is not true in this case)?
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
is name-index map, case-insensitive
Prop.next
is to point-to next node to be iterated over (-1 as terminator; because dict
is case-insensitive and this linked-list was added to resolve conflicts by case-sensitive search with fallback to first).
I see two options now:
- Implement custom iterator/enumerator, mscs/Roslyn is just not good enough now to see well and do its job. (No blame here, I can understand, not so important feature.)
- Drop the optimisation and just index it twice (once for
name
and second time fornext
). Maybe the compiler will get it and produce optimal machine code anyway. (I am creating scripting engine for Unity, this really is performance critical. Maybe it just checks the bounds once and uses ref/pointer-like access with no cost next time.)
And maybe 3. (2b, 2+1/2) Just copy the struct (32B on x64, three object references and two integers, but may grow, cannot see future). Probably not good solution (I either care and write the iterator or it is as good as 2.)
What I do understand:
The ref var p
cannot live after yield return
, because the compiler is constructing the iterator - a state machine, the ref
cannot be passed to next IEnumerator.MoveNext()
. But that is not the case here.
What I do not understand:
Why is such a rule enforced, instead of trying to actually generate the iterator/enumerator to see if such ref var
needs to cross the boundary (which it does not need here). Or any other way to do the job which looks doable (I do understand that what I imagine is harder to implement and expect answer to be: Roslyn folks have better things to do. Again, no offense, perfectly valid answer.)
Expected answers:
- Yes, maybe in the future / not worth it (create an Issue - will do if you find it worth it).
- There is better way (please share, I need solution).
If you want/need more context, it is for this project: https://github.com/evandisoft/RedOnion/tree/master/RedOnion.ROS/Descriptors/Reflect (Reflected.cs and Members.cs)
The reproducible example:
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)
{
}
}
}