1. 程式人生 > 其它 >值型別和引用型別

值型別和引用型別

技術標籤:精通C#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}");
        }
    }

結果如下:
在這裡插入圖片描述
傳遞引用型別的黃金規則:

  • 如果按引用傳遞引用型別,被呼叫者可能改變物件的狀態資料的值和引用的物件;
  • 如果按值傳遞引用型別,被呼叫者可能改變物件的狀態資料的值,但不能改變所引用的物件

多寫程式碼少打字!共勉!!!