C# 是否可以重新分配ref local?

C# 是否可以重新分配ref local?,c#,.net,pointers,C#,.net,Pointers,C#的ref局部变量是使用一个名为托管指针的CLR特性实现的,它有自己的一组限制,但幸运的是不可变不是其中之一。例如,在ILAsm中,如果您有一个托管指针类型的局部变量,则完全可以更改此指针,使其“引用”另一个位置。(C++/CLI还将此功能公开为。) 读取C#on ref局部变量在我看来,C#的ref局部变量是不可重定位的,即使基于CLR的托管指针;如果它们被初始化为指向某个变量,则不能使它们指向其他变量。我试过使用 ref object reference = ref some_var; r

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

读取C#on ref局部变量在我看来,C#的ref局部变量是不可重定位的,即使基于CLR的托管指针;如果它们被初始化为指向某个变量,则不能使它们指向其他变量。我试过使用

ref object reference = ref some_var;
ref reference = ref other_var;
和类似的构造,都没有用

我甚至尝试过在IL中编写一个小型结构来封装托管指针,就C#而言,它是有效的,但CLR似乎不喜欢在结构中包含托管指针,即使在我的使用中,它从未进入堆

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

[edit:][/strong>“ref reassign”是。我在下面讨论的“条件引用”解决方案是


我也一直为此感到沮丧,就在最近

本质上,在C#7.2中,您现在可以使用a进行初始化,这可能会有点麻烦地被合并到ref local reassignment的模拟中。当您在C#代码的词法范围内向下移动时,您可以通过多个变量向下“传递”ref local
赋值

这种方法需要大量的非常规思维和大量的提前计划。对于某些情况或编码场景,可能无法预测运行时配置的范围,因此任何条件分配方案都可能适用。在这种情况下,你就不走运了。或者,切换到,这将公开。这里的紧张之处在于,对于C#,通过引入托管指针的常规使用,可以立即实现简洁、优雅和效率方面无可争议的巨大收益(下面将进一步讨论这些要点)被克服重新分配问题所需的扭曲程度所浪费

接下来将展示我一直无法理解的语法。或者,检查我在顶部引用的

C#7.2参考通过三元执行器的本地条件赋值
?:


这一行来自提问者在这里提出的
ref local
困境的典型问题案例。它来自一段代码,该代码在遍历单个链接列表时维护反向指针。在<强> C/C++<强>中,任务是微不足道的,应该是(CSE101讲师非常喜爱的,也许是出于这种原因)——但使用托管指针C>

完全是令人痛苦的。 由于微软自己的C++/CLI语言向我们展示了.NET世界中的托管指针是多么的棒,这样的抱怨也是完全合法的。相反,大多数C#开发人员似乎只会在数组中使用整数索引,或者使用
safe
C#的完整本机指针

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

如图所示,列表的头将是一个独立的整数,与记录分开存储。在下一个代码段中,我将使用新的for来完成此操作。显然,使用这些整数链接向前遍历是没有问题的,但是C#传统上缺乏一种优雅的方式来保存到您所来自的节点的链接。这是一个问题,因为其中一个整数(第一个)是特殊情况,因为它没有嵌入到
节点
结构中

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
从舒适的阵列中进行成像,然后在完成后将每个图像恢复的(双倍)性能成本!毕竟,我们在这里使用值类型的原因当然是为了最大限度地提高性能。正如我所说,结构在
.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优化受到严格遵守。&lsqb;1] ,这要求每当存储位置公开时,CPU必须完全按照代码中编写的方式执行相关的获取序列。对于上一个示例,这意味着如果与共享
ix
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' },
    });
int ix = 1234;
arr[ix].a++;
arr[ix].b ^= arr[ix].c;
arr[ix].d /= (arr[lx].e + arr[ix].f);
ref Node rec = ref arr[1234];
rec.a++;
rec.b ^= rec.c;
rec.d /= (rec.e + rec.f);
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;
}