理解C#中的值类型和引用类型

C#编程中,经常会遇到需要将一个类的实例作为参数传递给函数的情况。但是,如果一个函数接受一个类的实例作为"ref"参数,这通常意味着开发者没有理解值类型和引用类型之间的区别。在C#中,类是引用类型,而结构体是值类型。这种区别对于理解如何正确使用"ref"关键字至关重要。

在C语言中,引入了"指针"的概念,而在C#中,引用类型的行为类似于C语言中的指针。指针是一个变量的内存地址,它允许通过地址来访问和修改变量的值。例如,在C语言中,可以通过以下方式使用指针:

int value = 15; int *p = &value

在上面的代码中,指针p被赋予了int变量value的地址。指针有左值(l-value)和右值(r-value)。在这个例子中,p的左值是value的地址,而右值是15。这允许将p传递给一个函数,在函数内部可以解引用指针的右值:

int copy = *p;

这个语句的意思是"将p的右值赋给copy"。想象一下,如果有一个函数接受一个int指针作为参数:

void foo(int *pointer_to_value) { *pointer_to_value = 5; }

在函数foo中,可以通过解引用指针来赋予一个新的右值。这会导致原始变量value的值变为5。

为什么在C#中需要"ref"参数

C#中,"ref"关键字用于按引用传递参数(就像指针一样),而不是按值传递。例如,可以将foo函数重写为:

void foo(ref int pointer_to_value) { pointer_to_value = 5; }

... 并且可以像这样按引用传递value变量:

foo(ref value);

现在,为了强调本文的重点,让看看在面试开发人员时喜欢使用的代码片段。

代码示例

以下是三个几乎相同的代码片段。唯一的区别是:

第一种情况:"foo"是一个类(引用类型

第二种情况:"foo"是一个结构体(值类型

第三种情况:"foo"仍然是一个结构体,但是通过"ref"关键字按引用传递

namespace ValueVsRefType { class Example { internal class foo { internal int x { get; set; } internal int y { get; set; } } static void Main(string[] args) { foo value = new foo(); Console.WriteLine("Before calling ChangeFoo(): foo.x = " + value.x + " and foo.y = " + value.y); ChangeFoo(value); Console.WriteLine("After calling ChangeFoo(): foo.x = " + value.x + " and foo.y = " + value.y); } static void ChangeFoo(foo parameter) { parameter.x = 10; parameter.y = 20; } } } namespace ValueVsRefType { class Example { internal struct foo { internal int x { get; set; } internal int y { get; set; } } static void Main(string[] args) { foo value = new foo(); Console.WriteLine("Before calling ChangeFoo(): foo.x = " + value.x + " and foo.y = " + value.y); ChangeFoo(value); Console.WriteLine("After calling ChangeFoo(): foo.x = " + value.x + " and foo.y = " + value.y); } static void ChangeFoo(foo parameter) { parameter.x = 10; parameter.y = 20; } } } namespace ValueVsRefType { class Example { internal struct foo { internal int x { get; set; } internal int y { get; set; } } static void Main(string[] args) { foo value = new foo(); Console.WriteLine("Before calling ChangeFoo(): foo.x = " + value.x + " and foo.y = " + value.y); ChangeFoo(ref value); Console.WriteLine("After calling ChangeFoo(): foo.x = " + value.x + " and foo.y = " + value.y); } static void ChangeFoo(ref foo parameter) { parameter.x = 10; parameter.y = 20; } } }

从上面的代码片段中,可以看到,当传递一个引用类型(类)时,实际上是在传递一个指针,而在传递一个值类型(结构体)时,实际上是在传递一个值的副本。

建议

建议是:仅在传递值类型参数时使用"ref"关键字!

技术细节

所有这些都是有趣的,但有没有想过底层的机制是什么?实际上,这一切都与栈的机制有关。如果曾经使用过(System.Collections)Stack类,就会知道如何"push"值(保存操作)和"pop"值(恢复操作)。在编译代码中,当一个函数被调用时,首先将所有参数"push"到栈上。稍后,在函数内部,引用程序栈中的值,而不是堆中的值。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485