IT系统集成商

系统集成 | 虚拟化应用 | 群晖网络存储 | 视频会议 | 数据防泄密 | 技术运维

手机: 130-1534-6247   电话: 0351-2396570

C#函数参数传递很容易混淆的概念

C#中的数据类型分为值类型引用类型两大类。
 
值类型:int,float,bool,char,enum,struct都是值类型。
 
引用类型:string,数组,class,接口,委托。其中的string是比较特殊的引用类型。C#给它增加个字符恒定的特性。
 
值类型:直接存储数据的值,保存在内存中的stack(堆栈)。
 
引用类型:存储对值的引用,实际上存储的就是一个内存的地址。引用类型的保存分成两块,实际值保存在托管堆(heap)中。实际值的内存地址保存在stack中
 
C#函数的参数如果不加ref,out这样的修饰符显式申明参数是通过引用传递外,默认都是值传递
 
这里要注意的一个问题是,参数的类型是值类型还是引用类型和传参数时用值传递还是引用传递是两个不同的概念
 
假如有void ChangeArray(int [] array) 和void ChangeInt(int a) 这两个函数。参数array是引用类型,a是值类型。但是他们传递时都是按值传递。
 
我们来举个例子说明下

按值传递参数:

class Program
    {
        public static void ChangeInt(int num)
        {
            num = 100;
        }
 
        public static void ChangeArray(int[] arraynew)
        {
            arraynew[0] = 10;
            arraynew= new int[] { 6, 7, 8, 9 };
        }
 
        static void Main(string[] args)
        {
            int anum = 1;
            int[] array = { 1, 2, 3 };
            ChangeInt(anum);
            ChangeArray(array);
            Console.WriteLine("value of num: " + anum);
            Console.Write("value of array: ");
            foreach (int i in array)
                Console.Write(i + " ");
        }
 
    }
 
结果是:value of anum : 1
 
       value of array :10 2 3
 
可能看到结果会有点奇怪。我们一般认为值传递就是把值拷贝一份,然后不管在函数中对传入的参数做啥改变,参数之前的值不会受啥影响,所以anum没有变成123,仍然是1
 
但是array[0]为啥却变成10了呢?
参数传递,亿维讯达
前面我们有说到引用类型在内存中是保存为两个部分,一个是stack中的内存地址,另一个是heap中的实际值。用时我们只直接用stack中的值,我们假如stack中的值为0xabcdefgh ,就说是array指向它吧。 那么我们按值传递时就是把这个stack的值拷贝成另一份就假如是arraynew指向它吧。跟拷贝anum的值1一样。
 
但是我们操作内存地址这样的值时不会像整数一样直接操作它,而只会通过它去找heap中的实际值。
 
于是我们arraynew[0] = 10。改变了实际上还是heap中数组的值了。 但arraynew= new int [] {6,7,8,9}没有对之前传的array产生影响。这个操作的意义是在heap中重新开辟一块内存,保存着值6,7,8,9。 这这块内存的地址赋给arraynew,于是它之前的值0xabcdefgh被改写了。但array指的值stack值仍没变,仍是0xabcdefgh

按引用传递参数

 
可以用out或ref显式指定。它们大部分时候可以通用,只是有一点细小区别。
 
先用ref 来举例吧,还用上面的例子,只是加个了关键字ref
 
class Program
    {
        public static void ChangeInt(ref int num)
        {
            num = 100;
        }
 
        public static void ChangeArray(ref int[] arraynew)
        {
            arraynew[0] = 10;
            arraynew= new int[] { 6, 7, 8, 9 };
        }
 
        static void Main(string[] args)
        {
            int anum = 1;
            int[] array = { 1, 2, 3 };
            ChangeInt(ref anum);
            ChangeArray(ref array);
            Console.WriteLine("value of num: " + anum);
            Console.Write("value of array: ");
            foreach (int i in array)
                Console.Write(i + " ");
        }
 
    }
 
结果是:value of anum : 100
 
       value of array :6 7 8 9
 
跟按值传递的结果完全不同吧
 
num = 100我们是容易理解。我们再来说下array的值
 
按引用传递时array指向的stack中的值不会复制一份,而是直接传过去。这样arraynew[0]= 10这样赋值时也同样改变了heap中 1 2 3 的值,变为10 2 3,如果
 
没有arraynew = new int [] {6,7,8,9} 这个语句,则它的结果跟上面按值传递是完全一样的。但有个这句话后就不一样,我们知道上面说了它的含义,在heap中开辟一块新内存
 
值是6 7 8 9,而array指向的stack的值被改写了,改为指向保存6 7 8 9的内存地址了。那含有10 2 3的那一块内存其实还继续存在,只是没有谁引用到它了。到时垃圾回收器会把它回收的。
 
补充:
 
说下out 和ref的细小区别
 
ref 传进来的参数必须要先赋值。
 
像上面 的例子中如果这样写
 
int num;
 
ChangeInt(ref int num);
 
就会出错,必须先给num给个值1。
 
而且out传进来的参数可以不先赋值。
 
out num;
 
ChangeInt(out int num);是对的
 
另外还有个区别就是如果用out的时候ChangeInt函数中必须有某个地方给num赋值了,而用ref不一定需要在函数中给num赋值
 
其实这样做的目的很好理解。C#为了确保在任何情况下num必须有个值,不能为空。
 
因为用ref,在调用函数前必须保证参数有值,所以在函数中就不必要求它一定再赋值
 
而用out由于在调用函数前不用保证参数必须有值,所以在函数中必须保证给它个值
 
ChangeInt(ref int num)和ChangeInt(out int num)虽然不一样,但是不同共存,不能当作两个不同的函数
 
而ChangeInt(int num)和上面 的两个函数是完全不一样的,可以放到一起共存
 
这样的话调用的时候ref ,out这样的关键字不能省的。必须匹配