是否可以重新分配当地的裁判? - c#

C#的ref局部变量是使用CLR功能(称为托管指针)实现的,该功能具有其自己的一组限制,但幸运的是不可变的不是其中之一。即在ILAsm中,如果您具有托管指针类型的局部变量,则完全有可能更改此指针,使其“引用”另一个位置。 (C ++ / CLI还将此功能公开为interior pointers。)

在ref locals上读取C#documentation似乎对C#的ref locals来说是可重定位的,尽管它基于CLR的托管指针。如果将它们初始化为指向某个变量,则无法使它们指向其他变量。我尝试使用

ref object reference = ref some_var;
ref reference = ref other_var;

和类似的结构,都无济于事。

我什至尝试编写一个小的结构,在IL中包装一个托管指针,就C#而言,它可以工作,但是CLR似乎不喜欢在结构中具有托管指针,即使在我的用法中从来没有去过堆。

是否真的需要借助IL或递归技巧来克服这一问题? (我正在实现一个数据结构,该结构需要跟踪遵循了哪个指针,这是对托管指针的完美使用。)

c#大神给出的解决方案

[edit:]“ ref-reassign”是on the schedule for C# 7.3。我下面讨论的“条件引用”解决方法是deployed in C# 7.2。

我也一直对此感到沮丧,而最近stumbled on a workable answer。

从本质上讲,在C#7.2中,您现在可以使用ref locals初始化ternary operator,并且可以进行转换。有点折磨,成为ref-local重新分配的模拟。在C#代码的词法范围内向下移动时,可以通过多个变量向下“传递”参考本地分配。

这种方法需要大量非常规的思维和大量的计划。对于某些情况或编码方案,可能无法预期运行时配置的范围,以致于任何条件分配方案都可能适用。在这种情况下,您不走运。或者,切换到显示C++/CLI的managed tracking references。这里的紧张之处在于,对于C#,通过引入托管指针的常规用法(这些要点将在下文中进一步讨论)立即实现的简洁,优雅和高效的巨大而无可争辩的收获,随着所要求的扭曲程度而微不足道。克服重新分配问题。

接下来显示了让我困惑了很长时间的语法。或者,检查我在顶部引用的link。

通过三元运算符? :的C#7.2 ref-local条件分配

ref int i_node = ref (f ? ref m_head : ref node.next);

这条线来自发问者在此处提出的ref local困境的典型问题案例。它来自代码,这些代码在遍历单链接列表时会维护反向指针。在C / C ++中,这项任务应该是微不足道的(也许正是出于这种特殊原因,它还是CSE101讲师所钟爱的),但它完全是使用托管指针C#来完成的。

由于微软自己的C ++ / CLI语言向我们展示了.NET领域中多么出色的托管指针,所以这种抱怨也是完全合法的。相反,大多数C#开发人员似乎最终只是在数组中使用整数索引,或者当然是在unsafe C#中使用完整的本机指针。

关于链表漫游示例的一些简短评论,以及为什么有人会对这些托管指针产生如此大的麻烦感兴趣。我们假设所有节点实际上都是数组(ValueType,原位)中的结构,例如m_nodes = new Node[100];,因此每个next指针都是整数(其在数组中的索引)。

struct Node
{
    public int ix, next;
    public char data;

    public override String ToString() => 
              String.Format("{0}  next: {1,2}  data: {2}", ix, next, data);
};

如此处所示,列表的开头将是一个独立的整数,与记录分开存储。在下一个代码段中,我将为C#7 syntax使用新的ValueTuple。显然,使用这些整数链接来遍历是没有问题的,但是C#传统上缺乏保持与您所来自节点的链接的优雅方法。这是一个问题,因为整数之一(第一个整数)由于未嵌入Node结构而处于特殊情况。

static (int head, Node[] nodes) L =
    (3,
    new[]
    {
        new Node { ix = 0, next = -1, data = 'E' },
        new Node { ix = 1, next =  4, data = 'B' },
        new Node { ix = 2, next =  0, data = 'D' },
        new Node { ix = 3, next =  1, data = 'A' },
        new Node { ix = 4, next =  2, data = 'C' },
    });

此外,大概每个节点上都要进行大量的处理工作,但是您真的不想支付在舒适阵列家庭中对每个(可能很大)ValueType进行成像的(两倍)性能成本。完成后必须将每个图像成像回来!毕竟,我们在这里使用值类型的原因肯定是为了最大化性能。正如我discuss at length elsewhere on this site一样,结构在.NET中可能非常高效,但前提是您永远不要意外地将它们“抬起”到存储中。这很容易做到,并且可以立即破坏您的内存总线带宽。

不抬高结构的简单方法只是重复数组索引,如下所示:

int ix = 1234;
arr[ix].a++;
arr[ix].b ^= arr[ix].c;
arr[ix].d /= (arr[lx].e + arr[ix].f);

在这里,每个ValueType字段访问在每个访问上都被独立地取消引用。尽管这种“优化”的确避免了上述带宽损失,但是一遍又一遍地重复相同的数组索引操作可能会导致一组完全不同的运行时损失。现在,(机会)成本是由于不必要地浪费了周期造成的,其中.NET重新计算可证明的不变物理偏移量或对阵列执行冗余边界检查。

释放模式下的JIT优化可以通过识别并整合所提供代码中的冗余来缓解某些问题,甚至可以极大地缓解这些问题,但可能不如您所想或希望的那样(或最终意识到您不想要): JIT优化受到严格遵守.NET Memory Model。[1]的严格限制,这要求只要在公开可见的存储位置,CPU必须严格按照代码中的规定执行相关的提取序列。对于前面的示例,这意味着如果在对ix进行操作之前以任何方式将arr与其他线程共享,则JIT必须确保CPU实际上恰好触摸了ix存储位置6次,否更多,不少。

当然,JIT无法使用重复的源代码来解决另一个显而易见的widely-acknowledged problem问题,例如前面的示例。简而言之,它很丑陋,容易出错,并且难以阅读和维护。为了说明这一点,您甚至没有注意到我故意在前面的代码中放的bug?

接下来显示的代码的简洁版本不会产生类似“易于发现”的错误;相反,作为一个类,它完全排除了它们,因为现在根本不需要数组索引的变量。变量ix在下面不需要存在,因为1234仅使用一次。随之而来的是,我之前如此明确地引入的错误无法传播到此示例,因为它没有表达方式,其好处在于,不存在的错误不会引入错误(与“不存在的错误相反。” 。',这肯定是一个错误)

ref Node rec = ref arr[1234];
rec.a++;
rec.b ^= rec.c;
rec.d /= (rec.e + rec.f);

没有人会不同意这是一种进步。因此,理想情况下,我们希望使用托管指针直接在结构中直接读取和写入字段。一种方法是将所有的有意义的处理代码编写为ValueType本身的实例成员函数和属性,尽管出于某些原因,似乎很多人不喜欢这种方法。无论如何,现在对于C#7 ref当地人来说,这一点很重要。

✹✹✹

我现在意识到,完全解释此处所需的编程类型可能太过复杂,无法通过玩具示例进行显示,因此超出了StackOverflow文章的范围。因此,我将继续前进,以总结一下,我将介绍一些工作代码,其中显示了模拟托管指针的重新分配。摘自HashSet<T> reference source [直接链接]中.NET 4.7.1的快照的快照,我将在没有太多解释的情况下显示我的版本:

int v1 = m_freeList;

for (int w = 0; v1 != -1; w++)
{
    ref int v2 = ref (w == 0 ? ref m_freeList : ref m_slots[v1].next);

    ref Slot fs = ref m_slots[v2];

    if (v2 >= i)
    {
        v2 = fs.next;
        fs = default(Slot);
        v1 = v2;
    }
    else
        v1 = fs.next;
}

这只是工作代码中的任意示例片段,因此我不希望有人遵循它,但是要点是,指定为v1v2的'ref'变量在作用域块之间交织在一起,并且三元运算符用于协调它们如何向下流动。例如,循环变量w的唯一用途是处理在链表遍历开始时针对特殊情况激活了哪个变量(前面已经讨论过)。

再次,事实证明,这对现代C#的正常易用性和流动性来说是一个非常奇怪和受折磨的约束。耐心,决心和(正如我之前提到的)需要进行很多计划。

[1.]如果您不熟悉所谓的.NET Memory Model,强烈建议您看一看。我相信.NET在这方面的优势是其最引人注目的功能之一,一个隐藏的宝石以及一个(并非如此)秘密的超级大国,这些超级大国极大地使我们这些仍坚持1980年代精神的无所不知的朋友感到尴尬裸机编码。请注意一个具有讽刺意味的讽刺:对编译器优化的狂野或无穷无尽的攻击施加严格的限制可能最终使应用程序具有更好的性能,因为更强的约束向开发人员提供了可靠的保证。这些反过来意味着更强的编程抽象或建议高级设计范例,在这种情况下与并发系统相关。例如,如果有人同意在本机社区中,无锁编程在数十年间一直处于边缘地位,这也许是不守规矩的优化编译器的暴民是罪魁祸首吗?没有严格和定义明确的内存模型所提供的可靠确定性和一致性,很容易破坏该专业领域的进展,正如所指出的,这与不受约束的编译器优化有些矛盾。因此,这里的限制意味着该领域最终可以创新和发展。这是我在.NET中的经验,在那里,无锁编程已成为一种可行的,现实的,甚至最终成为日常工作的基本日常编程工具。

java.net.URI.create异常 - java

java.net.URI.create("http://adserver.adtech.de/adlink|3.0") 抛出java.net.URISyntaxException: Illegal character in path at index 32: http://adserver.adtech.de/adlink|3.0 虽然n…

LeetCode题解计算机为什么是基于二进制的?

可以是三进制么?二进制有什么好处?题解:为什么叫电子计算机?算盘应该没有二进制

LeetCode题解黑白圆盘

一个圆盘被涂上了黑白二色,两种颜色各占一个半圆。圆盘以一个未知的速度、按一个未知的方向旋转。你有一种特殊的相机可以让你即时观察到圆上的一个点的颜色。你需要多少个相机才能确定圆盘旋转的方向?题解:可以用一个相机即可

LeetCode题解圆上任取三点构成锐角三角形的概率

来自字节跳动的一道几何题题解:1/4

LeetCode题解深度优先遍历和回溯的关系?

深度优先遍历的范围更大还是回溯的范围更大?为什么?题解:我的理解是:dfs是回溯思想的一种体现- 回溯:是在整个搜索空间中搜索出可行解,在搜索过程中不断剪枝回退,这是回溯的思想,这个搜索空间并没有限制于特定的数据结构。- dfs:dfs是指特定的数据结构中如图,树(特殊的图)中搜索答案,范围限制在了特定的数据结构。个人拙见。