简体   繁体   English

CS8176:迭代器不能有按引用的局部变量

[英]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:我现在看到两个选项:

  1. Implement custom iterator/enumerator, mscs/Roslyn is just not good enough now to see well and do its job.实现自定义迭代器/枚举器,mscs/Roslyn 现在还不够好,无法看清并完成其工作。 (No blame here, I can understand, not so important feature.) (这里没有责怪,我能理解,不是那么重要的功能。)
  2. Drop the optimisation and just index it twice (once for name and second time for next ).放弃优化,只需将其索引两次(一次为name ,第二次为next )。 Maybe the compiler will get it and produce optimal machine code anyway.也许编译器会得到它并产生最佳的机器代码。 (I am creating scripting engine for Unity, this really is performance critical. Maybe it just checks the bounds once and uses ref/pointer-like access with no cost next time.) (我正在为 Unity 创建脚本引擎,这确实对性能至关重要。也许它只检查一次边界并在下次免费使用类似 ref/指针的访问。)

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:预期答案:

  1. Yes, maybe in the future / not worth it (create an Issue - will do if you find it worth it).是的,也许在未来/不值得(创建一个问题 - 如果你觉得值得就可以)。
  2. There is better way (please share, I need solution).有更好的方法(请分享,我需要解决方案)。

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.

相关问题 如何在 Unity 的协程中按引用局部变量? - How to have by-reference locals in coroutines in Unity? 无法使用值 C# 初始化引用变量 - Cannot initialize a by-reference variable with a value C# 在C#中将结构值传递给方法by-reference是可接受的优化吗? - Is passing a struct value to a method by-reference in C# an acceptable optimization? 将C#按引用类型转换为匹配的按引用类型 - Convert c# by-reference type to the matching non-by-reference type 如何声明用于Linq查询的按引用参数变量? - How do you declare a by-reference parameter variable for use in a Linq query? .aspx和.cs之间的引用 - Reference between .aspx and .cs 找不到我的代码有任何问题,但是我有八条错误消息。 四个 CS1519、两个 CS1001、CS1022 和 CS8124 - Cannot find any issues with my code but, I have a eight error messages. Four CS1519, two CS1001, CS1022 and CS8124 错误CS0236:字段初始化程序无法引用非静态字段,方法或属性 - error CS0236: A field initializer cannot reference the non-static field, method, or property 'A字段初始值设定项的概念原因不能引用非静态字段,方法或属性'CS0236错误 - Conceptual reason of the 'A field initializer cannot reference the non-static field, method, or property' CS0236 Error 在另一个解决方案中更新了.cs文件,并且无法弄清楚如何引用更新的版本 - Updated .cs file in another solution and cannot figure out how to reference updated version
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM