C# 值类型和引用类型详解

值类型和引用类型

纸上得来终觉浅,绝知此事要躬行!

1、值类型和引用类型的比较

问题值类型引用类型
这个类型分配在哪里分配在栈上分配在托管堆上
变量是怎么表示的值类型变量是本地存储的引用类型变量指向被分配的实例所占的内存
基类型是什么必须派生自System.ValueType可以派生自除System.ValueType的任何非密封类型
这个类型可以作为其他类型的基类吗不能,值类型总是密封的,不能被继承能,如果这个类型不是密封的,它就可以作为其他类型的基类
默认的参数传递行为是什么变量是按值传递的(一个变量的副本传入被调用的函数)对于值类型,对象按值复制。对于引用类型,引用按值复制
这个类型可以重写System.Object.Finalize()【终结器】吗不能,值类型不会放在堆上,因此不需要被终结可以间接地重写(类似于C++析构函数语法)
可以为这个类型定义构造函数吗可以,但是默认的构造函数被保留(也就是自定义构造函数必须全部带有参数)可以
这个类型的变量什么时候消亡当它们越出定义的作用域时当托管堆被垃圾回收时

2、值类型和引用类型存储

引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。

3、值类型

从下面代码的结果,可以看出,p1初始化的值为(1,2),然后赋值p1的数据给p2,这时候,会在栈上产生两个MutablePoint类型的副本,每一个都可以独立操作。所以在改变p2的Y值的时候,p1的数据不会发生任何改变。

using System;

public struct MutablePoint
{
    public int X;
    public int Y;

    public MutablePoint(int x, int y) => (X, Y) = (x, y);

    public override string ToString() => $"({X}, {Y})";
}

public class Program
{
    public static void Main()
    {
        MutablePoint p1 = new MutablePoint(1, 2);
        MutablePoint p2 = p1;
        p2.Y = 200;
        Console.WriteLine($"{nameof(p1)} after {nameof(p2)} is modified: {p1}");
        Console.WriteLine($"{nameof(p2)}: {p2}");

        MutateAndDisplay(p2);
        Console.WriteLine($"{nameof(p2)} after passing to a method: {p2}");
    }

    private static void MutateAndDisplay(MutablePoint p)
    {
        p.X = 100;
        Console.WriteLine($"Point mutated in a method: {p}");
    }
}
// Expected output:
// p1 after p2 is modified: (1, 2)
// p2: (1, 200)
// Point mutated in a method: (100, 200)
// p2 after passing to a method: (1, 200)

4、包含引用类型的值类型

从下面的例子来看,Number字段是值类型,List<string>集合是引用类型,值类型的数据存储在栈上,引用类型的数据存储的是对象的引用,所以number类型产生了n1、n2两个数据的副本,独立操作、互不影响。但是List<string>集合存储在堆上的引用,修改其中一个的值,都会影响另一个相同引用的数据。

using System;
using System.Collections.Generic;

public struct TaggedInteger
{
    public int Number;
    private List<string> tags;

    public TaggedInteger(int n)
    {
        Number = n;
        tags = new List<string>();
    }

    public void AddTag(string tag) => tags.Add(tag);

    public override string ToString() => $"{Number} [{string.Join(", ", tags)}]";
}

public class Program
{
    public static void Main()
    {
        var n1 = new TaggedInteger(0);
        n1.AddTag("A");
        Console.WriteLine(n1);  // output: 0 [A]

        var n2 = n1;
        n2.Number = 7;
        n2.AddTag("B");

        Console.WriteLine(n1);  // output: 0 [A, B]
        Console.WriteLine(n2);  // output: 7 [A, B]
    }
}

5、按值传递传引用类型(无参数修饰符)

查看对象p的结果,可以看到,调用void PersonValue(Person p)方法,修改了p的Age的值,这是因为参数p传递的是数据的引用,修改这个引用,对象p的数据也会随之发生改变。但是为什么PersonValue方法中的p = new Person(“p2”, 22);没有改变p的数据呢,这是因为这里相当于重新实例化了对象p,但是形参传递的是引用,这里无法修改,当前p的实例化只在当前作用域内可以生效,离开这个作用域以后就会被垃圾回收。所以,如果这里传递的形参为string类型(引用类型),数据的结果你应该也清楚了吧!

  class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person("p1", 11);
            Console.WriteLine("1*************");
            p.DisPlay();  //Name="p1",Age=11

         

            PersonValue(p);
            Console.WriteLine("2*************");
            p.DisPlay();  //Name="p1",Age=99
        }

        static void PersonValue(Person p)
        {
            p.Age = 99;
            Console.WriteLine("3*************");
            p.DisPlay();  //Name="p1",Age=99

            p = new Person("p2", 22);
            Console.WriteLine("4*************");
            p.DisPlay();  //Name="p2",Age=22
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public void DisPlay()
        {
            Console.WriteLine($"Name={Name},Age={Age}");
        }
    }

结果如下:
在这里插入图片描述

6、按引用传递传引用类型

下面这个例子使用ref参数修饰符来进行引用传递,从结果可以很明显地看出,void PersonValue(ref Person p)方法修改了对象p的引用在内存中的指向。

 class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person("p1", 11);
            Console.WriteLine("1*************");
            p.DisPlay();  //Name="p1",Age=11

         

            PersonValue(ref p);
            Console.WriteLine("2*************");
            p.DisPlay();  //Name="p1",Age=99
        }

        static void PersonValue(ref Person p)
        {
            p.Age = 99;
            Console.WriteLine("3*************");
            p.DisPlay();  //Name="p1",Age=99

            p = new Person("p2", 22);
            Console.WriteLine("4*************");
            p.DisPlay();  //Name="p2",Age=22
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public void DisPlay()
        {
            Console.WriteLine($"Name={Name},Age={Age}");
        }
    }

结果如下:
在这里插入图片描述
传递引用类型的黄金规则:

  • 如果按引用传递引用类型,被调用者可能改变对象的状态数据的值和引用的对象;
  • 如果按值传递引用类型,被调用者可能改变对象的状态数据的值,但不能改变所引用的对象

多写代码少打字!共勉!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值