C#值类型与引用类型在使用上的区别

  • 值类型与引用类型

为了探明两者区别,直接看代码:

    public class Object_1
    {
        private int m_Age;

        public int Age
        {
            get { return m_Age; }
            set { m_Age = value; }
        }

        private string m_Namr;

        public string Name
        {
            get { return m_Namr; }
            set { m_Namr = value; }
        }
    }
    public struct Struct_1
    {
        private int m_Age;

        public int Age
        {
            get { return m_Age; }
            set { m_Age = value; }
        }

        private string m_Namr;

        public string Name
        {
            get { return m_Namr; }
            set { m_Namr = value; }
        }
    }

在上面我们定义了一个类(Object_1)和一个结构体(Struct_1)。我们都知道类是引用类型,而结构体是值类型,所以接下来进行对比,我们让两者在Change()方法中改变值:

       private void Form1_Load(object sender, EventArgs e)
        {
            Object_1 obj = new Object_1();
            obj.Age = 1;
            obj.Name = "Mike";
            Struct_1 stru = new Struct_1();
            stru.Age = 2;
            stru.Name = "Tim";
            Change(obj,stru);
            MessageBox.Show("obj.Age:" + obj.Age + "\nobj.Name:" + obj.Name + "\nstru.Age:" + stru.Age + "\nstru.Name:" + stru.Name);
        }

        public void Change(Object_1 obj_1, Struct_1 stru_1)
        {
            obj_1.Age += 10;
            obj_1.Name += "_Obj";
            stru_1.Age += 10;
            stru_1.Name += "_Stru";
        }

得到结果:


可以发现,obj.Age与obj.Name的值发生了改变,但是stru.Age与stru.Name的值依然与之前一样。

在Change()方法内部obj_1与stru_1的值都被改变,但是为什么在执行完Change()之后却出现差异。那可以设想为我们所改变的stru_1实际上不是真正的源参数。查询资料后得知:

那是因为在Change()方法内部obj_1被视为与传进obj参数是一个对象,而stru_1只是将参数stru的值Copy了一份。所以我们在内部修改stru_1的值只是修改了这个“替身”的值,本体的值并未修改


所以也可以得出结论:

值类型在方法体内作为参数被时,只是将值Copy一份使用,因而对其所做操作都无法对源参数产生影响。

引用类型在方法体内被作为参数时,内部对象与参数对象一致,对其所做的操作会影响到源参数。



PS:int型,long型,char型等数据类型都是struct,所以符合我们上面的结论。


  • ref与out

继续深入:

有时,我们也希望能将值类型像引用类型一样使用,比如在方法内修改值类型也影响源参数。

C#内相应提供了两个关键词:ref,out  (两者区别参考这里

当使用这两个关键词之后,我们看看有什么变化,修改上面的代码如下:

        private void Form1_Load(object sender, EventArgs e)
        {
            Object_1 obj = new Object_1();
            obj.Age = 1;
            obj.Name = "Mike";
            Struct_1 stru = new Struct_1();
            stru.Age = 2;
            stru.Name = "Tim";
            Change(obj,ref stru);//此处被修改
            MessageBox.Show("obj.Age:" + obj.Age + "\nobj.Name:" + obj.Name + "\nstru.Age:" + stru.Age + "\nstru.Name:" + stru.Name);
        }

        public void Change(Object_1 obj_1, ref Struct_1 stru_1)//此处被修改
        {
            obj_1.Age += 10;
            obj_1.Name += "_Obj";
            stru_1.Age += 10;
            stru_1.Name += "_Stru";
        }
结果如下:



stru.Age与stru.Name的值发生了变化。这也达到了我们需求,值类型也可以像引用类型一样使用。

但是原因是什么?

原因在于方法体在执行时,编译器根据ref关键词将值类型与参数对象“等同”了起来,而不是将值Copy一份。

这所有的一切究其原因,是在方法体内有种机制,这种机制将参数加上"签名"(可以理解为与身份证号类似的唯一标识),当是引用类型时,签名与外侧参数相同,值类型时则使用不同的“签名”。除非使用了ref或out关键词,值类型的“签名”才与源参数一致。


  • new对象

刚刚我们说的都是改变值,若是将new对象呢?继续修改上面Change()方法代码如下:

        public void Change(Object_1 obj_1, ref Struct_1 stru_1)
        {
            obj_1 = new Object_1();//此处被修改
            obj_1.Age += 10;
            obj_1.Name += "_Obj";
            stru_1.Age += 10;
            stru_1.Name += "_Stru";
        }
结果如下:



由于在Change()内obj_1对象被new了,所以新对象的“签名”与源参数的“签名”已经不同,所以方法内部值的改变不影响原来的对象。如果要使new对象影响源参数,依然可以使用ref

       public void Change(ref Object_1 obj_1, ref Struct_1 stru_1)


  • string类型

有人说string类型是特殊的引用类型,因为它与值类型一样在方法内部改变后无法影响原参数,为了验证,继续修改代码如下:

     private void Form1_Load(object sender, EventArgs e)
        {
            Object_1 obj = new Object_1();
            obj.Age = 1;
            obj.Name = "Mike";
            Struct_1 stru = new Struct_1();
            stru.Age = 2;
            stru.Name = "Tim";
            string str = "A";//此处被修改
            Change(ref obj, ref stru, str);
            MessageBox.Show("obj.Age:" + obj.Age + "\nobj.Name:" + obj.Name + "\nstru.Age:" + stru.Age + "\nstru.Name:" + stru.Name + "\nstr:" + str);//此处被修改
         }

        public void Change(ref Object_1 obj_1, ref Struct_1 stru_1, string str)//此处被修改
        {
            obj_1 = new Object_1();
            obj_1.Age += 10;
            obj_1.Name += "_Obj";
            stru_1.Age += 10;
            stru_1.Name += "_Stru";
            str = "B";//此处被修改
        }

结果如下:



表面上str的值没有改变,依然是“A”。但是不要只看表面,之所以依然显示为“A”,,是因为在这里string=运算符实际上等同于str=new String(new char[]{'B'});也就是new了一个新对象, 那str的“签名”也自然就与源参数不同,也就不会影响源参数(与 obj_1 = new Object_1()是同样的道理)。


posted @ 2013-12-18 10:24  Hi-Jimmy  阅读(53)  评论(0编辑  收藏  举报