值型別和引用型別
阿新 • • 發佈:2020-12-24
值型別和引用型別
紙上得來終覺淺,絕知此事要躬行!
1、值型別和引用型別的比較
問題 | 值型別 | 引用型別 |
---|---|---|
這個型別分配在哪裡 | 分配在棧上 | 分配在託管堆上 |
變數是怎麼表示的 | 值型別變數是本地儲存的 | 引用型別變數指向被分配的例項所佔的記憶體 |
基型別是什麼 | 必須派生自System.ValueType | 可以派生自除System.ValueType的任何非密封型別 |
這個型別可以作為其他型別的基類嗎 | 不能,值型別總是密封的,不能被繼承 | 能,如果這個型別不是密封的,它就可以作為其他型別的基類 |
預設的引數傳遞行為是什麼 | 變數是按值傳遞的(一個變數的副本傳入被呼叫的函式) | 對於值型別,物件按值複製。對於引用型別,引用按值複製 |
這個型別可以重寫System.Object.Finalize()【終結器】嗎 | 不能,值型別不會放在堆上,因此不需要被終結 | 可以間接地重寫(類似於C++解構函式語法) |
可以為這個型別定義建構函式嗎 | 可以,但是預設的建構函式被保留(也就是自定義建構函式必須全部帶有引數) | 可以 |
這個型別的變數什麼時候消亡 | 當它們越出定義的作用域時 | 當託管堆被垃圾回收時 |
2、值型別和引用型別儲存
引用型別的變數儲存對其資料(物件)的引用,而值型別的變數直接包含其資料。
3、值型別
從下面程式碼的結果,可以看出,p1初始化的值為(1,2),然後賦值p1的資料給p2,這時候,會在棧上產生兩個MutablePoint型別的副本,每一個都可以獨立操作
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}");
}
}
結果如下:
傳遞引用型別的黃金規則:
- 如果按引用傳遞引用型別,被呼叫者可能改變物件的狀態資料的值和引用的物件;
- 如果按值傳遞引用型別,被呼叫者可能改變物件的狀態資料的值,但不能改變所引用的物件
多寫程式碼少打字!共勉!!!