在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"关键字用于按引用传递参数(就像指针一样),而不是按值传递。例如,可以将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"到栈上。稍后,在函数内部,引用程序栈中的值,而不是堆中的值。