C#不可變類(lèi)型深入解析
學(xué)過(guò)C#的人都知道string類(lèi)型,但是string作為一種特殊的引用類(lèi)型還有一個(gè)重要的特征就是恒定性,或者叫不可變性,即Immutable。作為不可變類(lèi)型,最主要的特性表現(xiàn)是:一旦創(chuàng)建,只要修改,就會(huì)在托管堆上創(chuàng)建一個(gè)新的對(duì)象實(shí)例,而且和上一個(gè)對(duì)象實(shí)例是相鄰的,在托管堆上分配到一塊連續(xù)的內(nèi)存空間。
那么為什么需要不可變類(lèi)型呢?
在多線(xiàn)程情況下,一個(gè)線(xiàn)程,由于種種原因(比如異常)只修改了一個(gè)變量所代表類(lèi)型的部分成員的值,這時(shí)候,另一個(gè)進(jìn)程進(jìn)來(lái),也訪(fǎng)問(wèn)這個(gè)變量,第二個(gè)進(jìn)程訪(fǎng)問(wèn)到的變量成員,一部分成員還是原來(lái)的值,另一部分成員的值是第一個(gè)線(xiàn)程修改的值,這樣就出現(xiàn)了"數(shù)據(jù)不一致"。而不可變類(lèi)型就是為了解決在多線(xiàn)程條件下的"數(shù)據(jù)不一致"的問(wèn)題。
當(dāng)然,字符串的不可變性或恒定性,不僅解決了"數(shù)據(jù)不一致"的問(wèn)題,還為字符串的"駐留"提供了前提,這樣才可以把不同的字符串以及托管堆上的內(nèi)存地址以鍵值對(duì)的形式放到全局哈希表中。
一、親眼目睹"數(shù)據(jù)不一致":
對(duì)Student的Score屬性,在賦值的時(shí)候加上檢測(cè),檢測(cè)是否是2位數(shù)整數(shù)。
public struct Student
{
private string name;
private string score;
public string Name
{
get { return name; }
set { name = value; }
}
public string Score
{
get { return score; }
set
{
CheckScore(value);
score = value;
}
}
//檢測(cè)分?jǐn)?shù)是否是2位數(shù)整數(shù)
private void CheckScore(string value)
{
string pattern = @"\d{2}";
if (!Regex.IsMatch(value, pattern))
{
throw new Exception("不是有效分?jǐn)?shù)!");
}
}
public override string ToString()
{
return String.Format("姓名:{0},分?jǐn)?shù):{1}", name, score);
}
}
在主程序中故意制造出一個(gè)異常,目的是只對(duì)一個(gè)變量所代表類(lèi)型的某些成員賦值。
static void Main(string[] args)
{
Student student = new Student();
student.Name = "張三";
student.Score = "80";
Console.WriteLine(student.ToString());
try
{
student.Name = "李四";
student.Score = "8";
}
catch (Exception)
{
throw;
}
Console.WriteLine(student.ToString());
Console.ReadKey();
}
打斷點(diǎn),運(yùn)行,發(fā)現(xiàn)Student類(lèi)型的student變量,在第二次賦值的時(shí)候,把student的Name屬性值改了過(guò)來(lái),而student的Score屬性,由于發(fā)生了異常,沒(méi)有修改過(guò)來(lái)。這就是"數(shù)據(jù)不一致"。
如下圖所示:

二、動(dòng)手設(shè)計(jì)不可變類(lèi)型
1.不可變類(lèi)型的2個(gè)特性:
①對(duì)象的原子性:要么不改,要改就把所有成員都改,從而創(chuàng)建新的對(duì)象。
②對(duì)象的常量性:對(duì)象一旦創(chuàng)建,就不能改變狀態(tài),即不能改變對(duì)象的屬性,只能創(chuàng)建新的對(duì)象。
2.遵循以上不可變類(lèi)型的2個(gè)特征
①在構(gòu)造函數(shù)中對(duì)所有字段賦值。
②將屬性中的set訪(fǎng)問(wèn)器刪除。
class Program
{
static void Main(string[] args)
{
Student student = new Student("張三", "90");
student = new Student("李四","80");
Console.WriteLine(student.ToString());
Console.ReadKey();
}
}
public struct Student
{
private readonly string name;
private readonly string score;
public Student(string name, string score)
{
this.name = name;
this.score = score;
}
public string Name
{
get { return name; }
}
public string Score
{
get { return score; }
}
public override string ToString()
{
return String.Format("姓名:{0},分?jǐn)?shù):{1}", name, score);
}
}
運(yùn)行結(jié)果如下圖所示:

由此可見(jiàn),我們無(wú)法修改Student的其中某一個(gè)成員,只能通過(guò)構(gòu)造函數(shù)創(chuàng)建一個(gè)新對(duì)象,滿(mǎn)足"對(duì)象的原子性"。
而且也無(wú)法修改Student對(duì)象實(shí)例的某個(gè)屬性值,符合"對(duì)象的常量性"。
3.如果有引用類(lèi)型字段和屬性,如何做到"不可變性"?
class Program
{
static void Main(string[] args)
{
string[] classes = {"語(yǔ)文", "數(shù)學(xué)"};
Student student = new Student("張三", "85", classes);
Console.WriteLine("==修改之前==");
Console.WriteLine(student.ToString());
string[] tempArray = student.Classes;
tempArray[0] = "英語(yǔ)";
Console.WriteLine("==修改之后==");
Console.WriteLine(student.ToString());
Console.ReadKey();
}
}
public struct Student
{
private readonly string name;
private readonly string score;
private readonly string[] classes;
public Student(string name, string score, string[] classes)
{
this.name = name;
this.score = score;
this.classes = classes;
}
public string Name
{
get { return name; }
}
public string Score
{
get { return score; }
}
public string[] Classes
{
get { return classes; }
}
public override string ToString()
{
string temp = string.Empty;
foreach (string item in classes)
{
temp += item + ",";
}
return String.Format("姓名:{0},總分:{1},參加的課程有:{2}", name, score,temp.Substring(0, temp.Length -1));
}
}
結(jié)果如下圖所示:

由此可見(jiàn),還是可以對(duì)對(duì)象的屬性間接修改賦值,不滿(mǎn)足不可變類(lèi)型的"常量性"特點(diǎn)。
4.通過(guò)在構(gòu)造函數(shù)和屬性的get訪(fǎng)問(wèn)器中復(fù)制的方式來(lái)滿(mǎn)足不可變性
class Program
{
static void Main(string[] args)
{
string[] classes = {"語(yǔ)文", "數(shù)學(xué)"};
Student student = new Student("張三", "85", classes);
Console.WriteLine("==修改之前==");
Console.WriteLine(student.ToString());
string[] tempArray = student.Classes;
tempArray[0] = "英語(yǔ)";
Console.WriteLine("==修改之后==");
Console.WriteLine(student.ToString());
Console.ReadKey();
}
}
public struct Student
{
private readonly string name;
private readonly string score;
private readonly string[] classes;
public Student(string name, string score, string[] classes)
{
this.name = name;
this.score = score;
this.classes = new string[classes.Length];
classes.CopyTo(this.classes, 0);
CheckScore(score);
}
public string Name
{
get { return name; }
}
public string Score
{
get { return score; }
}
public string[] Classes
{
get
{
string[] result = new string[classes.Length];
classes.CopyTo(result,0);
return result;
}
}
//檢測(cè)分?jǐn)?shù)是否是2位數(shù)整數(shù)
private void CheckScore(string value)
{
string pattern = @"\d{2}";
if (!Regex.IsMatch(value, pattern))
{
throw new Exception("不是有效分?jǐn)?shù)!");
}
}
public override string ToString()
{
string temp = string.Empty;
foreach (string item in classes)
{
temp += item + ",";
}
return String.Format("姓名:{0},總分:{1},參加的課程有:{2}", name, score,temp.Substring(0, temp.Length -1));
}
}
運(yùn)行結(jié)果如下圖所示:

此外,如果讓分?jǐn)?shù)不滿(mǎn)足條件,Student student = new Student("張三", "8", classes),就會(huì)報(bào)錯(cuò):

相關(guān)文章
在C#中根據(jù)HardwareID獲取驅(qū)動(dòng)程序信息的實(shí)現(xiàn)代碼
這篇文章主要介紹了C#中根據(jù)HardwareID獲取驅(qū)動(dòng)程序信息的實(shí)現(xiàn)代碼,需要的朋友可以參考下2016-12-12
C#使用ZXing.Net實(shí)現(xiàn)生成二維碼和條碼
ZXing用Java實(shí)現(xiàn)的多種格式的一維二維條碼圖像處理庫(kù),而ZXing.Net是其.Net版本的實(shí)現(xiàn),下面我們就來(lái)看看 C#如何使用ZXing.Net實(shí)現(xiàn)生成二維碼和條碼吧2023-12-12
C#實(shí)現(xiàn)SMTP服務(wù)發(fā)送郵件的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)SMTP服務(wù)發(fā)送郵件的功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12
基于C#實(shí)現(xiàn)手機(jī)號(hào)碼歸屬地接口調(diào)用
這篇文章主要介紹了基于C#實(shí)現(xiàn)手機(jī)號(hào)碼歸屬地接口調(diào)用的相關(guān)資料,需要的朋友可以參考下2016-02-02

