如何解决CS8176:迭代器不能具有按引用引用的本地语言
在给定代码中是否存在此错误的真正原因,或者仅仅是在通用用法中可能会出错,在跨用法步骤中需要引用(在这种情况下是不正确的)? / p>
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
不区分大小写,并且此链表已添加,以通过区分大小写的搜索解决冲突(首先回退)。
我现在看到两个选项:
- 实施自定义迭代器/枚举器,mscs / Roslyn现在还不够好,无法很好地完成工作。 (我可以理解,这不是很重要,不是那么重要的功能。)
- 放弃优化并对其进行两次索引(一次
name
,第二次next
)。也许编译器会得到它并产生最佳的机器代码。 (我正在为Unity创建脚本引擎,这确实对性能至关重要。也许它只检查一次边界并在下一次免费使用类似ref / pointer的访问。)
可能是3。(2b,2 + 1/2)只需复制struct (x64上为32B,三个对象引用和两个整数,但可能会增长,看不到未来)。可能不是一个好的解决方案(我要么在乎并编写迭代器,要么等于2。)
我的理解
ref var p
不能在yield return
之后生存,因为编译器正在构造迭代器-状态机,因此ref
无法传递到下一个IEnumerator.MoveNext()
。但这不是事实。
我不明白的地方:
为什么要强制执行此类规则,而不是尝试实际生成迭代器/枚举器以查看此类ref var
是否需要越过边界(此处不需要)。或通过其他任何可行的方式完成工作(em)(我确实理解我想像的更难实现,并且期望答案是:罗斯林人有更好的事情要做。再次,没有冒犯,完全正确的答案。)
预期答案:
如果您想/需要更多的背景信息,请参考此项目: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)
{
}
}
}
解决方法
编译器要使用局部变量作为字段重写迭代器块,以保留状态,并且不能将引用类型作为字段。是的,您说对了,它没有跨过yield
,所以从技术上讲,它可以可以重新编写以根据需要重新声明,但这对非常让人记住的复杂规则,其中简单的更改会破坏代码。毯子上的“不”更容易被骗。
在这种情况下(或类似的async
方法),解决方法通常是一个辅助方法;例如:
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;
}
}
}
或
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);
}
}
}
本地函数不受迭代器规则的约束,因此您可以使用ref-locals。
,该引用不能传递到下一个IEnumerator.MoveNext()。但这不是事实。
编译器创建一个状态机类来保存运行时继续下一个迭代所需的数据。不能包含ref成员的那个类。
编译器可以检测到该变量仅在有限范围内需要,并且不需要添加到该状态类中,但是正如Marc在其回答中所说,这是一个非常昂贵的功能额外的好处。请记住,features start at -100 points。因此,您可以提出要求,但请务必说明其用途。
对于此设置而言,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",// 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;
}
}
}
结果:
| Method | Mean | Error | StdDev |
|-------------------------- |----------:|---------:|---------:|
| EnumerateRefLocalFunction | 94.83 ns | 0.138 ns | 0.122 ns |
| Enumerate | 98.00 ns | 0.285 ns | 0.238 ns |
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。