C#中方法参数的引用传递、值传递。

一、值类型和引用类型

C# 中的类型一共分为两类,一类是值类型(Value Type),一类是引用类型(Reference Type)。

值类型包括结构体(struct)和枚举(enum)。
引用类型包括类(class)、接口(interface)、委托(delegate)、数组(array)等。

常见的简单类型如short、int、long、float、double、byte、char等其本质上都是结构体,对应struct System.Int16、System.Int32、System.Int64、System.Single、System.Double、Syetem.Byte、System.Char,因此它们都是值类型。但string和object例外,它们本质上是类,对应class System.String和System.Object,所以它们是引用类型。

1. 值类型

值类型变量本身保存了该类型的全部数据,当声明一个值类型的变量时,该变量会被分配到栈(Stack)上。

2. 引用类型

引用类型变量本身保存的是位于堆(Heap)上的该类型的实例的内存地址,并不包含数据。当声明一个引用类型变量时,该变量会被分配到栈上。如果仅仅只是声明这样一个变量,由于在堆上还没有创建该类型的实例,因此,变量值为null,意思是不指向任何类型实例(堆上的对象)。对于变量的类型声明,用于限制此变量可以保存的类型。

二、值传递和引用传递

C#中方法的参数传递默认的是值传递,引用传递和输出传递需要在参数类型前面对应加上ref、out限制符,由于输出传递和引用传递类似,这里只讨论引用传递。

值传递参数是原变量的拷贝,值传递参数和原变量的内存地址不同,因此方法中对值传递参数的修改不会改变原变量。

引用传递参数是原变量的指针,引用传递参数和原变量的内存地址相同,相应的方法中对引用传递参数的修改会改变原变量。

三、传递值类型参数

1. 通过值传递值类型
class PassingValByVal
{
    private static void Change(int x)
    {
        x = 10;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    public static void Execute()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);
        Change(n);
        System.Console.WriteLine("The value after calling the method: {0}", n);
        System.Console.ReadLine();
    }
}

这里写图片描述

  • 原变量 n 是 int 值类型,系统为原变量 n 在堆栈Stack上分配的内存地址为:0x088aed2c,该内存地址存储的数据值为5。

这里写图片描述

  • 当调用 Change 方法时,由于是值传递,系统会为局部参数变量 x 在堆栈Stack上分配一个新的内存区域, 内存地址为:0x088aecd0,并将 n 中的数据值 5 复制到局部参数变量 x 中。这里可以看到局部参数变量 x 和原变量 n 的内存地址是不同的。

这里写图片描述

  • 对局部参数变量 x 的赋值操作是对变量内存地址中的数据值进行修改,此处是将局部参数变量 x 的内存地址 0x088aecd0 中的数据值由原来的 5 改为 10。

这里写图片描述

  • 因为原变量 n 和局部参数变量 x 并不是同一块内存区域(内存地址不同),所以 Change 方法中对局部参数变量 x 的赋值操作不会影响到原变量 n。n 的值在调用 Change 方法前后是相同的。实际上,方法内发生的更改只影响局部参数变量 x。

这里写图片描述

2. 通过引用传递值类型
class PassingRefByVal
{
    private static void Change(ref int x)
    {
        x = 10;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    public static void Execute()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);
        Change(ref n);
        System.Console.WriteLine("The value after calling the method: {0}", n);
        System.Console.ReadLine();
    }
}

这里写图片描述

  • 原变量 n 是 int 值类型,系统为原变量 n 在堆栈Stack上分配的内存地址为:0x0877eb4c,该内存地址存储的数据值为5。

这里写图片描述

  • 当调用 Change 方法时,由于是引用传递,局部参数变量 x 的内存地址为原变量 n 的内存地址 0x0877eb4c,故局部参数变量 x 的值即是原变量 n 的值。这里可以看到局部参数变量 x 和原变量 n 的内存地址是相同的。

这里写图片描述

  • 对局部参数变量 x 的赋值操作是对变量内存地址中的数据值进行修改,此处是将局部参数变量 x 的内存地址 0x0877eb4c 中的数据值由原来的 5 改为 10。这里监视窗口中 *&n 的值没有改变是因为在 Change 方法代码块中访问不到原变量 n ,数值没有刷新。

这里写图片描述

  • 因为原变量 n 和局部参数变量 x 是同一块内存区域(内存地址相同),所以调用 Change 方法后,可以看到变量 n 的内存地址 0x0877eb4c 中的数据值为 10。

输出

四、传递引用类型参数

1. 通过值传递引用类型
class ClassA
{
    public int a;
    public ClassB classB;
}
class ClassB
{
    public int b;
}
class PassingRefByVal
{
    private static void Change(ClassA tempClassA)
    {
        tempClassA.a = 1;
        tempClassA.classB.b = 1;

        tempClassA = new ClassA();
        tempClassA.a = 2;
        tempClassA.classB = new ClassB();
        tempClassA.classB.b = 2;

        System.Console.WriteLine("The value inside the method: a = {0} ,b = {1}", tempClassA.a, tempClassA.classB.b);
    }
    public static void Execute()
    {
        ClassA classA = new ClassA();
        classA.a = 0;
        classA.classB = new ClassB();
        classA.classB.b = 0;

        System.Console.WriteLine("The value before calling the method: a = {0} ,b = {1}", 
                        classA.a, classA.classB.b);
        Change(classA);
        System.Console.WriteLine("The value after calling the method: a = {0} ,b = {1}", 
                        classA.a, classA.classB.b);
        System.Console.ReadLine();
    }
}

这里写图片描述

  • 原变量 classA 是 ClassA 引用类型,系统为原变量 classA 在堆栈Stack上分配的内存地址为:0x086ce92c,该内存地址存储的数据值为 37492360(十六进制为 0x023c1688),该数据值是系统分配在托管堆Heap上的 ClassA 类型实例的内存地址。从图中可以看到 classA.a 的内存地址是0X023c1690,0x023c1688~0x023c1690保存了 ClassA 类型的方法表指针和 SyncBlockIndex 。

这里写图片描述

  • 当调用 Change 方法时,由于是值传递,系统会为局部参数变量 tempClassA 在堆栈Stack上分配一个新的内存区域, 内存地址为:0x086ce8c0,并将 classA 中的数据值 37492360(十六进制为 0x023c1688) 复制到局部参数变量 tempClassA 中。这里可以看到局部参数变量 tempClassA 和原变量 classA 的内存地址是不同的,但由于它们的内存地址中的数据值相同(均为 37492360,十六进制为0x023c1688),所有它们指向了同一个内存地址,也就是同一个 ClassA 类型实例。

这里写图片描述
这里写图片描述

  • 因为局部参数变量 tempClassA 和原变量 classA 指向了同一个 ClassA类型实例,所以 tempClassA.a 和 classA.a、tempClassA.classB.b 和 classA.classB.b 是同一块内存地址,因此对 tempClassA.a 和 tempClassA.classB.b 的赋值操作同时也是对 classA.a 和 classA.classB.b 的赋值操作。这里监视窗口中 classA.a 和 classA.classB.b 的数据值还没有刷新。

这里写图片描述

  • new运算符创建了一个新的 ClassA 类型的实例,系统为新实例在托管堆Heap上分配了另一块内存区域,内存地址为 37631124(十六进制为 0x023e3494),赋值操作将新实例的内存地址覆盖 tempClassA 内存地址中的数据值,即 tempClassA 指向了新的对象。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

  • 因为局部参数变量 tempClassA 指向了新创建的实例,后续对 tempClassA 的修改只会对新实例的内存区域的数值进行修改,而不会影响到原变量 classA。

这里写图片描述

2. 通过引用传递引用类型
class PassingRefByRef
{
    private static void Change(ref ClassA tempClassA)
    {
        tempClassA.a = 1;
        tempClassA.classB.b = 1;

        tempClassA = new ClassA();
        tempClassA.a = 2;
        tempClassA.classB = new ClassB();
        tempClassA.classB.b = 2;

        System.Console.WriteLine("The value inside the method: a = {0} ,b = {1}", 
                        tempClassA.a, tempClassA.classB.b);
    }
    public static void Execute()
    {
        ClassA classA = new ClassA();
        classA.a = 0;
        classA.classB = new ClassB();
        classA.classB.b = 0;

        System.Console.WriteLine("The value before calling the method: a = {0} ,b = {1}", 
                        classA.a, classA.classB.b);
        Change(ref classA);
        System.Console.WriteLine("The value after calling the method: a = {0} ,b = {1}", 
                        classA.a, classA.classB.b);
        System.Console.ReadLine();
    }
}
  • 同理,引用传递的局部参数变量的内存地址为原变量的内存地址,后续对局部参数的修改同时也是对原变量的修改,故最后 classA.a 和 classA.classB.b 的值为2。

五、拷贝

拷贝即是通常所说的复制(Copy)或克隆(Clone),对象的拷贝也就是从当前对象复制一个“一模一样”的新对象出来。虽然都是复制对象,但是不同的复制方法,复制出来的新对象却并非完全一模一样,对象内部存在着一些差异。通常的拷贝方法有两种,即深拷贝和浅拷贝。

在浅拷贝中,拷贝对象会复制当前对象本身的数据(包括子对象的引用,拷贝对象和当前对象引用同一个子对象),但子对象的数据不会被复制。在深拷贝中,拷贝对象会复制当前对象所有的数据,包括当前对象子对象的数据;可以看出,深拷贝和浅拷贝之间的区别在于是否复制了子对象。

1. 浅拷贝

通过 MemberwiseClone 方法创建一个浅拷贝,该方法会创建一个新对象,然后将当前对象的非静态字段复制到新的对象。 如果一个字段是值类型,则执行该字段的逐位复制。 如果一个字段是引用类型,则将引用复制但被引用的对象不会复制;因此,原始对象和其克隆引用同一对象。

class ClassA
{
    public int a;
    public ClassB classB;

    public ClassA ShallowCopy()
    {
        return (ClassA)MemberwiseClone();
    }
}
class TestShallowCopy
{
    public static void Execute()
    {
        ClassA classA = new ClassA();
        classA.a = 0;
        classA.classB = new ClassB();
        classA.classB.b = 0;

        ClassA copy = classA.ShallowCopy();
        System.Console.WriteLine("The value before change: a = {0} ,b = {1}", 
                        copy.a, copy.classB.b);
        classA.a = 1;
        classA.classB.b = 1;
        System.Console.WriteLine("The value after change: a = {0} ,b = {1}", 
                        copy.a, copy.classB.b);
        System.Console.ReadLine();
    }
}

这里写图片描述

2. 深拷贝
class ClassA
{
    public int a;
    public ClassB classB;

    public ClassA DeepCopy()
    {
        ClassA copy = new ClassA();
        copy.a = a;
        copy.classB = new ClassB();
        copy.classB.b = classB.b;
        return copy;
    }
}
class TestDeepCopy
{
    public static void Execute()
    {
        ClassA classA = new ClassA();
        classA.a = 0;
        classA.classB = new ClassB();
        classA.classB.b = 0;

        ClassA copy = classA.DeepCopy();
        System.Console.WriteLine("The value before change: a = {0} ,b = {1}", 
                        copy.a, copy.classB.b);
        classA.a = 1;
        classA.classB.b = 1;
        System.Console.WriteLine("The value after change: a = {0} ,b = {1}", 
                        copy.a, copy.classB.b);
        System.Console.ReadLine();
    }
}

这里写图片描述

3. ICloneable 接口

ICloneable 接口使您可以提供一个自定义的实现用于创建一个现有对象的拷贝。包含一个成员 Clone 方法,旨在提供超出 Object.MemberwiseClone 方法的克隆支持。

class ClassA : ICloneable
{
    public int a;
    public ClassB classB;

    public object Clone()
    {
        ClassA copy = new ClassA();
        copy.a = a;
        copy.classB = new ClassB();
        copy.classB.b = classB.b;
        return copy;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值