c# - CS8176 : Iterators cannot have by-reference locals

标签 c# ref enumerator

在给定的代码中是否存在此错误的真正原因,或者只是在一般使用中可能会出错,其中跨交互器步骤需要引用(在这种情况下并非如此)?

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 创建脚本引擎,这确实对性能至关重要。也许它只检查一次边界并在下次免费使用类似引用/指针的访问。)

  • 也许是 3. (2b, 2+1/2) 只需复制结构(x64 上的 32B,三个对象引用和两个整数,但可能会增长,看不到 future )。可能不是很好的解决方案(我要么关心并编写迭代器,要么与 2 一样好。)
    我所了解的:ref var p以后不能住yield return ,因为编译器正在构建迭代器——一个状态机,ref不能传递给下一个 IEnumerator.MoveNext() .但这里的情况并非如此。
    我不明白的地方:
    为什么要强制执行这样的规则,而不是尝试实际生成迭代器/枚举器以查看是否这样 ref var需要越过边界(这里不需要)。或者任何其他看起来可行的工作方式(我确实理解我想象的更难实现并期望答案是:罗斯林人有更好的事情要做。再次,没有冒犯,完全有效的答案。)
    预期答案:
  • 是的,也许在 future /不值得(创建一个问题 - 如果你觉得值得就会这样做)。
  • 有更好的方法(请分享,我需要解决方案)。

  • 如果您想要/需要更多上下文,则适用于此项目: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)
            {
            }
        }
    }
    

    最佳答案

    the ref cannot be passed to next IEnumerator.MoveNext(). But that is not the case here.


    编译器创建一个状态机类来保存运行时所需的数据,以继续下一次迭代。那个类不能包含 ref 成员。
    编译器可以检测到变量只在有限的范围内需要,而不需要添加到该状态类中,但正如 Marc 在他们的回答中所说,这是一个昂贵的功能,几乎没有额外的好处。记住,features start at -100 points .所以你可以要求它,但一定要解释它的用途。
    对于它的值(value),对于此设置,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;
                }
            }
        }
    
    结果:
    |                    Method |      Mean |    Error |   StdDev |
    |-------------------------- |----------:|---------:|---------:|
    | EnumerateRefLocalFunction |  94.83 ns | 0.138 ns | 0.122 ns |
    |                 Enumerate |  98.00 ns | 0.285 ns | 0.238 ns |
    

    关于c# - CS8176 : Iterators cannot have by-reference locals,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63728889/

    相关文章:

    c# - Parallel.For 不使用我的主线程

    c# - 将列表参数作为 ref 传递

    ruby - 如何简化此 Enumerator 代码?

    c# - Visual Studio 如何在不中断其 IEnumerator<T> 的 MoveNext 的情况下评估 IEnumerable?

    ruby 1.8.7 : Enumerator next complains about number of arguments

    c# - 带有 Angular 6 的 .Net Framework 4.5 中的 CORS 问题

    c# - 如何在 .cscfg 文件中设置 Windows azure - 辅助角色的数据库连接字符串..?

    c# - 更改 DropDownlist 时没有为一个或多个必需参数指定值

    c# - 与 ref 传递的参数互操作

    c++ - 使用 boost::ref 来指示编码约定中的意图