一、概述
C#的参数传递是一个不大不小的问题,一旦掌握不清楚,很容易出现一些低级错误,排查起来相当麻烦。这里就将参数传递的一些问题说明一下,组织目录如下:
1.c#参数分类
2.传递参数的几种方式及对参数的影响
二、主要内容
1.参数分类
通用系统类型(common type system CTS)主要分为两大类:值类型和引用类型。结构图如下:
从图中,我们可以知道:CTS类型不是值类型,就是引用类型
c#对这两种类型数据的存储是不一样的:对于值类型,将直接存储在托管栈中(stack),对于引用类型,只将对数据的引用(地址)存在托管栈中,真实的数据存在于托管堆(heap)。如图所示:
分配在托管栈中的变量,会在创建它们的方法结束时,自动释放。而分配在托管堆中的变量,并不会在创建它们的方法结束时释放内存,而是有垃圾回收机制在将来的某个时候去释放内存。上面标红的这句话是我们分析后面代码的基础,请好好理解。
2.参数传递的几种方式
传递参数有按值传递和按引用传递之分,这样,和数据类型简单的组合一下,便得到以下4种传递方式:
(1)按值传递值类型。
(2)按引用传递值类型。
(3)按值传递引用类型。
(4)按引用传递引用类型。
一般来说,除非使用特定的关键字(ref和out。关于它们的微小差别,可以参考后面给出的第二个链接),否则参数是按值传递的,也就是说,会传递一个副本。
下面根据代码来分析每种方式对参数的影响:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace test
{
class Program
{
#region<span style="font-family: Arial, Helvetica, sans-serif;">//点类</span>
class point
{
//属性
int X{get;set;}
int Y{get;set;}
//构造函数
public point(int x,int y)
{
X = x;
Y = y;
}
//设置坐标
public void set(int x,int y)
{
X = x;
Y = y;
}
//输出点
public void output()
{
Console.WriteLine("("+X.ToString()+","+Y.ToString()+")");
}
}
#endregion
//按值传递值类型数据
static void value_transmit_value(int i)
{
i = 3;
}
//按引用传递值类型数据
static void refer_transmit_value(ref int i)
{
i = 3;
}
//按值传递引用类型数据
static void value_transmit_refer(point pt)
{
pt.set(5, 5);
}
//按引用传递引用类型数据
static void refer_transmit_refer(ref point pt)
{
pt.set(5, 5);
}
//按值传递引用类型数据,并改变参数指向
static void newpt(point pt)
{
point ptnew = new point(9, 9);
pt = ptnew;
}
//按引用传递引用类型数据,并改变参数指向
static void newpt(ref point pt)
{
point ptnew = new point(9, 9);
pt = ptnew;
}
#region 测试代码
static void Main(string[] args)
{
//1.
int number = 0;
value_transmit_value(number);
Console.WriteLine(number);//按值传递并未改变其值
refer_transmit_value(ref number);//按引用传递改变了值
Console.WriteLine(number);
point pt = new point(0,0);
//对于引用类型不管是按值传递还是按引用传递都改变了参数值
value_transmit_refer(pt);
pt.output();
refer_transmit_refer(ref pt);
pt.output();
//2.对于赋值,不管是按值传递还是按引用传递都改变了参数和原始对象的值。
point pt_1 = pt;
value_transmit_refer(pt_1);
pt_1.output();
pt.output();
refer_transmit_refer(ref pt_1);
pt_1.output();
pt.output();
//3.按值传递没有改变参数值
newpt(pt);
pt.output();
//4.按引用传递改变了参数值
newpt(ref pt);
pt.output();
//5.对于赋值,按值传递没有改变了参数和原始对象的值。
point pt_2 = pt;
newpt(pt_2);
pt_2.output();
pt.output();
//6.对于赋值,按引用传递改变了参数值,没有改变原始对象的值。
point pt_3 = pt;
newpt(ref pt_3);
pt_3.output();
pt.output();
}
#endregion
}
}
运行结果:
1.
2.
3.
4.
5.
6.
对参数的影响:
1.测试代码里有一个point类,可以通过set方法改变坐标值。然后有一系列方法测试传递参数值。
2.对于值类型的参数,很简单,按值传递不会改变参数值(因为传递的是副本),按引用传递会改变参数值。
3.对于引用类型的参数,对于修改参数本身,不管是按值传递还是按引用类型传递,都会改变参数值。(因为最终都会根据地址修改托管堆里的数据);
4.当方法内部开辟了新的对象,并使参数引用它时,按值传递并没有改变参数值,按引用传递却改变了参数值,见结果3和结果4.(因为按值传递给的是副本,并不会改变参数内容(地址),所以存储的值也就不会改变。按引用传递改变了其内容(地址),使其指向了新的对象,所以值改变 了。原来的对象因为没有引用,会被垃圾回收机制回收。)
5.如果参数本身是一个引用(在方法外部引用),当方法内部开辟了新的对象,并使参数引用它时,按值传递没有改变参数值和原始对象值,按引用传递改变了参数值,没有改变原始对象值,见结果5和结果6.(原理同上,按值传递不会改变其内容(地址)。按引用传递改变了地址,但也仅仅是该参数指向的地址改变了,不再指向原始对象的数据,所以参数值改变了,但原始对象不会改变。(因为其地址未改变))
另外通过结果3,4,5,6可以知道,不管何种传递都不会影响到原始对象的值,只可能影响到引用它的参数的值。这也从侧面验证了上面的分析。3,4,5的分析很冗长,其实只需要知道两点就可以了:1.按值传递的,传递的是参数的副本,按引用传递,可以直接操作参数。2.值类型,将直接存储在托管栈中(stack),对于引用类型,只将对数据的引用(地址)存在托管栈中,真实的数据存在于托管堆(heap)。根据这两点就可以很轻松的掌握参数传递规律,希望对你有所帮助: )。