C# 传值和传引用 ( ref out in )

为了简单起见,下面的示例使用 ref。

通过值传递引用类型

arr。

class PassingRefByVal 
{
    static void Change(int[] pArray)
    {
        pArray[0] = 888;  // 这里赋值 改变 原来pArray[0]的值
        pArray = new int[5] { -3, -1, -2, -3, -4 };   // 这里的赋值,只在该方法内有效
        System.Console.WriteLine("在Change方法里面,arr第一个元素的值是:{0}", pArray[0]);

        pArray[1] = 666;  // 这里赋值 不会改变 原来pArray[1]的值,只是改变这里新创建的数组的第一个元素的值
        System.Console.WriteLine("在Change方法里面,arr第二个元素的值是:{0}", pArray[1]);
    }

    static void Main()
    {
        int[] arr = { 1, 4, 5 };
        System.Console.WriteLine("在Main方法里面,调用Change方法前,arr数组中第一个元素的值是:{0}", arr[0]);
        System.Console.WriteLine("在Main方法里面,调用Change方法前,arr数组中第二个元素的值是:{0}", arr[1]);

        Change(arr);
        System.Console.WriteLine("在Main方法里面,调用Change方法后,第一个元素的值是:{0}", arr[0]);
        System.Console.WriteLine("在Main方法里面,调用Change方法后,第二个元素的值是:{0}", arr[1]);

        System.Console.ReadLine();
    }
}

/* 输出:
    在Main方法中,调用Change方法之前,pArray中第一个元素的值是:1
    在Main方法中,调用Change方法之前,pArray中第二个元素的值是:4
    在Change方法中,pArray中第一个元素的值是:-3
    在Change方法中,pArray中第二个元素的值是:666
    在Main方法中,调用Change方法后,pArray中第一个元素的值是:888
    在Main方法中,调用Change方法后,pArray中第二个元素的值是:4
*/

Change 方法内。

通过引用传递引用类型 

在方法中做的更改会影响到原始的变量。

class PassingRefByRef
{
    static void Change(ref int[] pArray)
    {
        // 下面的更改都会影响到变量的原始值
        pArray[0] = 888;
        pArray = new int[5] { -3, -1, -2, -3, -4 };
        System.Console.WriteLine("在Change方法中,pArray中第一个元素的值是:{0}", pArray[0]);
    }

    static void Main()
    {
        int[] arr = { 1, 4, 5 };
        System.Console.WriteLine("在Main方法中,调用Change方法之前,pArray中第一个元素的值是:{0}", arr[0]);

        Change(ref arr);
        System.Console.WriteLine("在Main方法中,调用Change方法后,pArray中第一个元素的值是:{0}", arr[0]);

        System.Console.ReadLine();
    }
}
/* Output:
    在Main方法中,调用Change方法之前,pArray中第一个元素的值是: 1
    在Change方法中,pArray中第一个元素的值是: -3
    在Main方法中,调用Change方法后,pArray中第一个元素的值是: -3
*/

Change 方法中创建的新的数组。

交换两个字符串 

    交换字符串是传递引用类型参数的很好的示例。

Main 内没有进行交换。

class SwapEmployees
{
    // 传值
    static void swap(string e1, string e2)
    {
        string temp = e1;
        e1 = e2;
        e2 = temp;
        System.Console.WriteLine("在swap方法里:{0} {1}", e1, e2);
    }

    static void Main()
    {
        string worker = "John";
        string manager = "Smith";
        System.Console.WriteLine("在Main方法里,调用swap方法之前:{0} {1}", worker, manager);

        swap(worker, manager);   // 通过引用传递方式,来传递字符串变量
        System.Console.WriteLine("在Main方法里,调用swap方法之后:{0} {1}", worker, manager);

        System.Console.ReadLine();
    }
}

/* Output:
    在Main方法里,调用swap方法之前:John Smith
    在swap方法里:Smith John
    在Main方法里,调用swap方法之后:John Smith
*/

    通过值传递时,对应的内存变化如图一所示:

C# 传值和传引用 ( ref out in )

图一:通过值传递引用类型参数时的内存变化

Main 内均进行了交换。

class SwapEmployees
{
    // 传引用
    static void swap(ref string e1, ref string e2)
    {
        string temp = e1;
        e1 = e2;
        e2 = temp;
        System.Console.WriteLine("在swap方法里:{0} {1}", e1, e2);
    }

    static void Main()
    {
        string worker = "John";
        string manager = "Smith";
        System.Console.WriteLine("在Main方法里,调用swap方法之前:{0} {1}", worker, manager);

        swap(ref worker, ref manager);   // 通过引用传递方式,来传递字符串变量
        System.Console.WriteLine("在Main方法里,调用swap方法之后:{0} {1}", worker, manager);

        System.Console.ReadLine();
    }
}

/* Output:
   在Main方法里,调用swap方法之前:John Smith
   在swap方法里:Smith John
   在Main方法里,调用swap方法之后:Smith John
*/

     通过引用传递时,对应的内存变化如图二所示:

C# 传值和传引用 ( ref out in )

图二:通过引用传递引用类型参数时的内存变化 

总结:

上面的两种方式与值类型和引用类型的存储方式密切相关。关于值类型和引用类型的存储方式的区别,可以参考文章:http://www.xuexila.com/baikezhishi/536966.html    

//摘录其中一段
    与 C++ 不同,Java 自动管理栈和堆,程序员不能直接地设置栈或堆。
  Java 的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过 new, newarray, anewarray, multianewarray 等指令建立,它们不需要程序代码来显式的释放。堆由垃圾回收来负责,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
  栈的优势是,存取速度比堆要快,仅次于寄存器,栈的数据还可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。
  栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
   int a = 3;
   int b = 3;
  编译器先处理 int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理 int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了 a 与 b 同时均指向3的情况。这时,如果再令 a = 4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令 a 指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量 。

 

官方文档:

https://docs.microsoft.com/en-us/dotnet/csharp/reference-semantics-with-value-types?view=netcore-2.1#ref-struct-type