目錄
頂級語句(C#9.0)
using
全局using指令(C#10.0)
using資源管理問題
using聲明(C#8.0)
using聲明陷阱
錯誤寫法
正確寫法
文件范圍的命名空間聲明(C#10.0)
可空引用類型(C#8.0)
使用
使用!抑制警告
init
record記錄類型(C#9.0)
?record的原理及深入
record深入
with拷貝
頂級語句(C#9.0)
- 直接在C#文件中直接編寫入口方法的代碼,不用類,不用Main。經典寫法仍然支持。
- 同一個項目中只能有一個文件具有頂級語句。
- 頂級語句中可以直接使用await語法,也可以聲明函數
using
全局using指令(C#10.0)
- 將 global 修飾符添加到 using 前,這個命名空間就應用到整個項目,不用重復using。
- 通常創建一個專門用來編寫全局using代碼的C#文件。
- 如果csproj中啟用了<ImplicitUsings>enable</ImplicitUsings>,編譯器會自動隱式增加對于System、System.Linq等常用命名空間的引入,不同各類型項目引入的命名空間也不一樣。
using資源管理問題
實現了IDisposible接口的對象可以用using進行管理。
問題:如果一段代碼中有很多非托管資源需要被釋放的話,代碼中就會存在著多個嵌套的using語句。
using聲明(C#8.0)
在實現了Idisposable/IAsyncDisposable接口的類型的變量聲明前加上using,當代碼執行離開變量的作用域時,對象就會被釋放。
using聲明陷阱
錯誤寫法
using var outSteam = File.OpenWrite("e:/1.txt");
using var writer = new StreamWriter(outSteam);
writer.WriteLine("Hello");
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);
正確寫法
using (var outSteam = File.OpenWrite("e:/1.txt"))
using (var writer = new StreamWriter(outSteam))
{writer.WriteLine("Hello");
}
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);{using var outSteam = File.OpenWrite("e:/1.txt");using var writer = new StreamWriter(outSteam);writer.WriteLine("Hello");
}
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);
文件范圍的命名空間聲明(C#10.0)
在之前版本的C#中,類型必須定義在namespace中。
namespace NewGrammar;
class Book
{public int Id { get; set; }public string Name { get; set; }
}
可空引用類型(C#8.0)
C#數據類型分為值類型和引用類型兩種,值類型的變量不可以為空,而引用類型變量可以為空。
問題:如果不注意檢查引用類型變量是否可空,就有可能造成程序中出現NullReferenceException異常。
使用
- csproj中<Nullable>enable</Nullable>啟用可空引用類型檢查。
- 在引用類型后添加“?”修飾符來聲明這個類型是可空的。對于沒有添加“?”修飾符的引用類型的變量,如果編譯器發現存在為這個變量賦值null的可能性的時候,編譯器會給出警告信息。
class Book
{public int Id { get; set; }public string? Name { get; set; }
}
使用!抑制警告
如果程序員確認被訪問的變量、成員確實不會出現為空的情況,也可以在訪問可空的變量、成員的時候加上!來抑制編譯器的警告。不要濫用。
init
僅在對象構造期間為屬性或索引器元素賦值,一旦初始化對象,將無法更改。
class Pig
{public int id { get; init; }
}
record記錄類型(C#9.0)
C#中的==運算符默認是判斷兩個變量指向的是否是同一個對象,即使兩個對象內容完全一樣,也不相等。可以通過重寫Equals方法、重寫==運算符等來解決這個問題,不過需要開發人員編寫非常多的額外代碼。
2、在C#9.0中增加了記錄(record)類型的語法,編譯器會為我們自動生成Equals、GetHashcode等方法。
namespace SelfReferecingOrganizationalStructureTree;record Book(int Id, string Name);class Program
{static void Main(string[] args){Book b1=new Book(1,"a1");Book b2=new Book(1,"a1");Book b3=new Book(2,"a1");Console.WriteLine(b1.ToString());Console.WriteLine(b1==b2);Console.WriteLine(b2==b3);Console.WriteLine(object.ReferenceEquals(b1, b2));}
}
?record的原理及深入
- 編譯器會根據Book類型中的屬性定義,自動為Book類型生成包含全部屬性的構造方法。注意,默認情況下,編譯器會生成一個包含所有屬性的構造方法,因此,我們編寫new Book()、new Book(“a1”)這兩種寫法都是不可以的。也會生成ToString方法和Equals等方法。
- 通過反編譯看背后原理。避免反編譯器的優化,需要把反編譯器生成的代碼改成C#8.0的語法。結論:record就是普通的一個類。
- record數據類型為我們提供了為所有屬性賦值的構造方法,所有屬性都是只讀的,而且對象可以進行值相等性比較,并且提供了可讀性強的ToString()返回值。在需要編寫一些不可變類并且需要進行對象值比較的對象時候,record可以幫我們把代碼的編寫難度大大降低。
record深入
- 可以實現部分屬性是只讀的、而部分屬性是可以讀寫。
record Book(int Id, string Name) {public string? Author { get; set; } }class Program {static void Main(string[] args){Book b1 = new Book(1, "計算機網絡");b1.Author = "小明";Console.WriteLine(b1.ToString());} }
- 默認生成的構造方法的行為不能修改,我們可以為類型提供多個構造方法,然后其他構造方法通過this調用默認的構造方法。
record Book(int Id, string Name) {public string? Author { get; set; }public Book(int Id, string Name, string Author) : this(Id, Name){this.Author = Author;} }
- 也推薦使用只讀屬性的類型。這樣的所有屬性都為只讀的類型叫做“不可變類型”,可以讓程序邏輯簡單,減少并發訪問、狀態管理等的麻煩。
with拷貝
- record也是普通類,變量的賦值是引用的傳遞。這是和結構體不同之處。
- 生成一個對象的副本,這個對象的其他屬性值與原對象的相同,只有一個或者少數幾個屬性改變。
- 麻煩的做法:Book b1 = new Book(1, "計算機網絡", "小明");
用with關鍵字簡化:Book b2 = b1 with { Id = 2, Name = "操作系統" }; 創建的也是拷貝。
static void Main(string[] args)
{Book b1 = new Book(1, "計算機網絡", "小明");Book b2 = b1 with { Id = 2, Name = "操作系統" };Console.WriteLine(b1);Console.WriteLine(b2);
}