在C#中,ref和out与C++中的指针相同吗?

9
我刚刚用C#编写了一个类似于这样的Swap例程:
static void Swap(ref int x, ref int y)
{
    int temp = x;
    x = y;
    y = temp;
}

它与这段C++代码执行相同的操作:
void swap(int *d1, int *d2)
{
    int temp=*d1;
    *d1=*d2;
    *d2=temp;
}

那么,refout关键词在C#中是否像指针一样,而不需要使用unsafe代码?

7个回答

12
它们的使用范围更有限。你可以在指针上使用 ++,但不能在 ref 或 out 上使用。
编辑:评论中有些混淆,因此为了绝对清楚:这里的重点是与指针的能力进行比较。您无法执行与 ptr++ 相同的操作于 ref/out 上,即使它让其寻址相邻内存位置。当然(但这里并不相关),您可以执行 (*ptr)++ 的等价操作,但那是与值而不是指针的能力进行比较。
它们很可能在内部只是指针,因为堆栈不会被移动,并且 C# 组织得非常仔细,以使 ref 和 out 始终引用堆栈的活动区域。
编辑:再次明确一下(如果从下面的示例还不清楚的话),这里的重点不是 ref/out 只能指向堆栈。而是在它指向堆栈时,语言规则保证不会成为悬空指针。这种保证是必要的(并且在这里是相关/有趣的),因为堆栈根据方法调用退出丢弃信息,没有检查以确保任何引用仍然存在。
相反,当 ref/out 引用 GC 堆中的对象时,这些对象能够保持活动状态的时间长达所需的任何长度:GC 堆恰好是为保留对象所需的任何时间而设计的,并提供固定(请参见下面的示例)以支持不能被 GC 压缩程序移动的情况。
如果您曾经在不安全代码中使用互操作,您将发现 ref 与指针非常密切相关。例如,如果 COM 接口声明如下:
HRESULT Write(BYTE *pBuffer, UINT size);
互操作程序集将把它转换为以下内容:
void Write(ref byte pBuffer, uint size);

你可以使用以下方式调用它(我相信COM互操作性会负责固定数组):

byte[] b = new byte[1000];
obj.Write(ref b[0], b.Length);

换句话说,对于第一个字节的ref引用将让你访问到它的所有内容; 显然它是指向第一个字节的指针。


你可以对一个 ref 参数使用 ++,但这并不意味着相同的事情。 - Ben Voigt
此外,“refout始终指向堆栈的活动区域”是完全错误的。您自己的示例将创建一个对GC堆上对象的引用。 - Ben Voigt

6

C#中的引用参数可以替代指针的某些用法,但并非全部。

指针的另一个常见用途是作为迭代数组的手段。out/ref参数无法实现这一点,所以它们并不“与指针相同”。


3

refout只用于函数参数,表示该参数要通过引用传递而不是值传递。从这个意义上说,它们有点像C++中的指针(实际上更像引用)。在这篇文章中可以了解更多相关信息。


2
使用 out 的好处在于你可以保证变量会被赋值,如果没有赋值则会编译错误。

2
实际上,我会将它们与C++引用进行比较,而不是指针。在C++和C中,指针是一个更一般的概念,而引用将实现您想要的功能。
当然,在底层,所有这些都无疑是指针。

从语法上讲,它们实际上更类似于指针,因为你必须添加ref,就像在C/C++中添加&以获取指针一样。 - Daniel Earwicker
(我指在调用站点处。) - Daniel Earwicker
所以它们实际上处于中间位置(因为在读取/写入时不需要解引用)。 - Pavel Minaev

1

虽然比较因人而异,但我认为不是。'ref' 改变了调用约定,但并未改变参数的类型。在你的 C++ 示例中,d1 和 d2 的类型为 int*。在 C# 中,它们仍然是 Int32,只是通过引用传递而不是按值传递。

顺便说一下,你的 C++ 代码并没有以传统意义上的方式交换输入。将其泛化如下:

template<typename T>
void swap(T *d1, T *d2)
{
    T temp = *d1;
    *d1 = *d2;
    *d2 = temp;
}

...除非所有类型T都有复制构造函数,否则它将无法工作,即使有复制构造函数,它也比交换指针要低效得多。


我认为你的比喻并不完全适用;在C++中考虑int *const int *。Const是一种限定符,它限制了应用于类型的使用,有效地将其转换为不同的类型。但是当C++编译为IL时,const被转换为类型上的一种自定义修饰符,而不是在CLI类型系统中定义不同的类型。这表明语言中的“类型”与运行时中的“类型”是分开的。同样,ref限制了您对对象的操作(例如,您不能在lambda中捕获它),这是C#类型系统的一部分,但不是CLI的。 - Daniel Earwicker

1

简短的回答是“是”(类似的功能,但机制并不完全相同)。 另外需要注意的是,如果您使用FxCop分析代码,使用outref将导致“Microsoft.Design”错误“CA1045:DoNotPassTypesByReference。”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接