最近学习刘铁猛老师的课踩了一个坑,重点记录梳理一下 传值参数方式中传递引用数据类型时与引用参数方式传递引用数据类型的底层区别。话不多说,直接上干货。
阅读本篇博客,默认读者拥有C#中值类型数据与引用类型数据在堆与栈上的分布基础知识。
传值参数
顾名思义,传值参数就是在C#方法定义中,以默认不加修饰符形式声明形参类型,同时在方法调用中,以默认方式传递参数值,即下图所示:
static Main(string[] args)
{
int a = 1;
Test(a);
Console.WriteLine("值:{0}", a);
}
// 定义方法
void Test(int num)
{
num++;
Console.WriteLine("值:{0}", num);
}
细分一下,在通过这样方式定义与使用方法时,可分为传递值类型数据与引用类型数据。
值类型
该形式传递值类型数据如下图所示:
static void Main(string[] args)
{
int num = 20;
Add(num);
Console.WriteLine("{0}",num);
}
static void Add(int num)
{
num+=100;
Console.WriteLine("值:{0}", num);
}
在方法内部,在底层会创建一个变量的副本,因此对值参数的操作永远不会影响到局部变量以外原参数的值,这个比较好理解,故省略画图解释步骤。
引用类型
传递引用类型数据分为两种情况如下图所示:
情况一:
static void Main(string[] args)
{
//传值参数,传引用类型
Student s1 = new Student();
s1.Name = "Jack";
Method(s1);
Console.WriteLine("s1:{0},{1}", s1.GetHashCode(), s1.Name);
}
static void Method(Student stu)
{
stu.Name = "Tom";
Console.WriteLine("stu:{0},{1}",stu.GetHashCode(), stu.Name);
}
class Student
{
public string Name { get; set; }
}
该情况底层情况如图所示:

在思考过程中,这里很容易与引用参数传递引用类型值时产生疑惑,因为它们两者变现的效果一致,且hashcode也一直,表明他们两者都指向了同一操作对象,但细细思考底层逻辑会发现,他们两者是有区别的,其根本区别就体现在传值参数方式与引用参数方式的根本区别上,即会不会为传入参数创建副本。在当前这种情况下在方法内对传入参数进行修改运行程序会发现,原参数对象值也被修改,其原因便是它们两者虽然在栈上地址不同,但他们两者在栈上的内存空间中存放了同一堆地址的值,即指向了同一个对象,因此对变量"stu"的修改也会反映在变量“s1“上。
情况二:
static void Main(string[] args)
{
//传值参数,传引用类型
Student s1 = new Student();
s1.Name = "Jack";
Method(s1);
Console.WriteLine("s1:{0},{1}", s1.GetHashCode(), s1.Name);
}
static void Method(Student stu)
{
Student stu = new Student();
stu.Name = "Tom";
Console.WriteLine("stu:{0},{1}",stu.GetHashCode(), stu.Name);
}
class Student
{
public string Name { get; set; }
}
底层情况如图所示:

该情况较为容易理解,故不作赘述。
引用参数
引用参数就是在C#方法定义中,显式地添加"ref"修饰符声明该参数传递方式为引用参数,同时在方法调用中,也需要显式地在传入的变量前添加"ref"修饰符,即下图所示:
static Main(string[] args)
{
int a = 1;
Test(ref a);
Console.WriteLine("值:{0}", a);
}
// 定义方法
void Test(ref int num)
{
num++;
Console.WriteLine("值:{0}", num);
}
在该形式传递参数中,底层不会为传递参数创建副本,形参变量名在底层上会直接指向原传入参数在栈中的地址。基于此无论是以下传递值类型数据还是引用类型数据都会比较容易理解。
值类型
该形式传递值类型数据如下图所示:
static void Main(string[] args)
{
int x = 1;
RefUpdate(ref x);
Console.WriteLine(x);
}
static void RefUpdate(ref int x)
{
//引用参数,局部变量x指向与传入参数相同的地址
x += 100;
}
该形式在方法内部,不会为传进来的参数创建副本,而是直接在原参数基础上进行操作,在方法内部直接修改值类型数据在栈中内存的值,因此对传入参数的操作会直接影响到局部变量以外原参数的值,这个也比较好理解,故省略画图解释步骤。
引用类型
static void Main(string[] args)
{
Student s1 = new Student();
s1.Name = "Jack";
RefUpdateClass(ref s1);
Console.WriteLine("s1:{0},{1}", s1.GetHashCode(), s1.Name);
}
static void RefUpdateClass(ref Student stu)
{
//引用参数,局部变量x指向与传入参数相同的地址
stu.Name = "Tom";
Console.WriteLine("stu:{0},{1}", stu.GetHashCode(), stu.Name);
}
底层情况如图所示:
该形式在方法内部,同样不会为传进来的参数创建副本,同样直接在原传入参数所指向堆中数据进行操作,其他部分与上面类似,故不作赘述。
总结
虽然在C#中在使用方法的过程中设置传值参数方式中传递引用数据类型时与引用参数方式传递引用数据类型很容易得到相同的处理结果,新手便会很容易误以为它们两者没有什么区别,但细细梳理下来便会发现他们两者在底层上的明显区别,能够加深对两者方法的理解。以上是个人经验见解,若有误欢迎与我在评论区讨论。
本文深入探讨C#中传值参数与引用参数在处理值类型与引用类型数据时的区别,尤其关注引用类型在两种参数传递方式下的底层行为差异。
4942

被折叠的 条评论
为什么被折叠?



