[英]CS8176: Iterators cannot have by-reference locals
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 dict
是名称索引映射,不区分大小写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). Prop.next
是指向要迭代的下一个节点(-1 作为终止符;因为dict
不区分大小写,并且添加此链表是为了通过区分大小写的搜索并回退到第一个来解决冲突)。
I see two options now:我现在看到两个选项:
name
and second time for next
).name
,第二次为next
)。 Maybe the compiler will get it and produce optimal machine code anyway. 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) .也许 3. (2b, 2+1/2) 只需复制结构(x64 上的 32B,三个对象引用和两个整数,但可能会增长,看不到未来) 。 Probably not good solution (I either care and write the iterator or it is as good as 2.)
可能不是好的解决方案(我要么关心并编写迭代器,要么与 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()
. ref var p
不能在yield return
之后存活,因为编译器正在构建迭代器 - 一个状态机, ref
不能传递给下一个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).为什么要强制执行这样的规则,而不是尝试实际生成迭代器/枚举器以查看此类
ref var
需要跨越边界(此处不需要)。 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:预期答案:
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)如果您想要/需要更多上下文,则适用于该项目: https : //github.com/evandisoft/RedOnion/tree/master/RedOnion.ROS/Descriptors/Reflect (Reflected.cs 和 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)
{
}
}
}
the ref cannot be passed to next IEnumerator.MoveNext().
ref 不能传递给下一个 IEnumerator.MoveNext()。 But that is not the case here.
但这里的情况并非如此。
The compiler creates a state machine class to hold the data needed at runtime to continue to the next iteration.编译器创建一个状态机类来保存运行时继续下一次迭代所需的数据。 It's that class that can't contain a ref member.
那个类不能包含 ref 成员。
The compiler could detect that the variable is only needed in a limited scope, and not needed to be added to that state class, but as Marc says in their answer, that's an expensive feature for little added benefit.编译器可以检测到变量只在有限的范围内需要,而不需要添加到该状态类中,但正如 Marc 在他们的回答中所说,这是一个昂贵的功能,几乎没有额外的好处。 Remember, features start at -100 points .
请记住, 功能从 -100 点开始。 So you could ask for it, but make sure to explain its use.
所以你可以要求它,但一定要解释它的用途。
For what it's worth, Marc's version is ~4% faster (according to BenchmarkDotNet ) for this setup:对于它的价值,对于此设置,Marc 的版本要快约 4%(根据BenchmarkDotNet ):
public class StructArrayAccessBenchmark
{
struct Prop
{
public string name;
public int next;
}
private readonly Prop[] _props =
{
new Prop { name = "1-1", next = 1 }, // 0
new Prop { name = "1-2", next = -1 }, // 1
new Prop { name = "2-1", next = 3 }, // 2
new Prop { name = "2-2", next = 4 }, // 3
new Prop { name = "2-2", next = -1 }, // 4
};
readonly Dictionary<string, int> _dict = new Dictionary<string, int>
{
{ "1", 0 },
{ "2", 2 },
};
private readonly Consumer _consumer = new Consumer();
// 95ns
[Benchmark]
public void EnumerateRefLocalFunction() => enumerateRefLocalFunction().Consume(_consumer);
// 98ns
[Benchmark]
public void Enumerate() => enumerate().Consume(_consumer);
public IEnumerable<string> enumerateRefLocalFunction()
{
(string value, int next) GetNext(int index)
{
ref var p = ref _props[index];
return (p.name, p.next);
}
foreach (int i in _dict.Values)
{
var (name, next) = GetNext(i);
yield return name;
while (next >= 0)
{
(name, next) = GetNext(next);
yield return name;
}
}
}
public IEnumerable<string> enumerate()
{
foreach (int i in _dict.Values)
{
var p = _props[i];
int next = p.next;
yield return p.name;
while (next >= 0)
{
p = _props[next];
next = p.next;
yield return p.name;
}
}
}
Results:结果:
| Method | Mean | Error | StdDev |
|-------------------------- |----------:|---------:|---------:|
| EnumerateRefLocalFunction | 94.83 ns | 0.138 ns | 0.122 ns |
| Enumerate | 98.00 ns | 0.285 ns | 0.238 ns |
The compiler wants to rewrite iterator blocks with locals as fields, to retain the state, and you cannot have ref-types as fields.编译器想用局部变量作为字段重写迭代器块,以保留状态,并且不能将引用类型作为字段。 Yes, you're right that it doesn't cross the
yield
so technically it could probably be rewritten to re-declare it as-needed, but that makes for very complex rules for humans to remember, where simple-looking changes break the code.是的,你是对的,它不会超过
yield
所以从技术上讲,它可能会被重写以根据需要重新声明它,但这使得人类要记住非常复杂的规则,其中看似简单的更改会破坏代码. A blanket "no" is much easier to grok.一揽子“不”更容易理解。
The workaround in this scenario (or similarly with async
methods) is usually a helper method;这种情况下的解决方法(或类似的
async
方法)通常是辅助方法; for example:例如:
IEnumerable<string> EnumerateStatic()
{
(string value, int next) GetNext(int index)
{
ref var p = ref props[index];
return (p.name, p.next);
}
foreach (int i in dict.Values)
{
(var name, var next) = GetNext(i);
yield return name;
while (next >= 0)
{
(name, next) = GetNext(next);
yield return name;
}
}
}
or或者
IEnumerable<string> EnumerateStatic()
{
string GetNext(ref int next)
{
ref var p = ref props[next];
next = p.next;
return p.name;
}
foreach (int i in dict.Values)
{
var next = i;
yield return GetNext(ref next);
while (next >= 0)
{
yield return GetNext(ref next);
}
}
}
The local function is not bound by the iterator-block rules, so you can use ref-locals. local 函数不受迭代器块规则的约束,因此您可以使用 ref-locals。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.