【发布时间】:2020-09-03 17:33:14
【问题描述】:
在给定的代码中是否存在此错误的真正原因,或者只是 在需要跨交互步骤进行引用的一般用法中可能会出错(在这种情况下不正确)?
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 是不区分大小写,并且添加了这个 linked-list 以通过区分大小写的搜索解决冲突,并回退到第一)。
我现在看到两个选项:
- 实现自定义迭代器/枚举器,mscs/Roslyn 现在还不够好,无法很好地了解并完成其工作。 (这里不怪,我能理解,不是那么重要的功能。)
- 放弃优化并将其索引两次(一次用于
name,第二次用于next)。也许编译器会得到它并生成最佳机器代码。 (我正在为 Unity 创建脚本引擎,这确实对性能至关重要。也许它只检查一次边界并使用类似 ref/pointer 的访问,下次无需任何成本。)
也许是 3. (2b, 2+1/2) 只需复制结构 (x64 上为 32B,三个对象引用和两个整数,但可能会增长,看不到未来)。可能不是好的解决方案(我要么关心并编写迭代器,要么它与 2 一样好。)
我的理解:
ref var p 不能在yield return 之后存在,因为编译器正在构造迭代器——一个状态机,ref 不能传递给下一个IEnumerator.MoveNext()。但这里不是这样。
我不明白的地方:
为什么要强制执行这样的规则,而不是尝试实际生成迭代器/枚举器以查看此类ref var 是否需要越界(此处不需要)。或任何其他方式来完成这项工作看起来可行(我确实理解我想象的更难实现并期望答案是:罗斯林人有更好的事情要做。同样,没有冒犯,完全有效的答案。)
预期答案:
- 是的,也许在未来/不值得(创建一个问题 - 如果您觉得值得,就这样做)。
- 有更好的方法(请分享,我需要解决方案)。
如果您想要/需要更多上下文,请参阅此项目: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)
{
}
}
}
【问题讨论】:
标签: c# ref enumerator