微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

CS8176:迭代器不能具有按引用引用的本地语言

如何解决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不区分大小写,并且此链表添加,以通过区分大小写的搜索解决冲突(首先回退)。

我现在看到两个选项:

  1. 实施自定义迭代器/枚举器,mscs / Roslyn现在还不够好,无法很好地完成工作。 (我可以理解,这不是很重要,不是那么重要的功能。)
  2. 放弃优化并对其进行两次索引(一次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)(我确实理解我想像的更难实现,并且期望答案是:罗斯林人有更好的事情要做。再次,没有冒犯,完全正确的答案。)

预期答案:

  1. 是的,也许在将来/不值得(创建一个问题-如果您发现值得就可以了)。
  2. 有更好的方法(请分享,我需要解决方案)。

如果您想/需要更多的背景信息,请参考此项目: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 举报,一经查实,本站将立刻删除。