C#6.0,C#7.0新特性
C#6.0新特性
-
-
- Auto-Property enhancements(自動屬性增強)
- Read-only auto-properties (真正的只讀屬性)
- Auto-Property Initializers (自動屬性的初始化)
- Expression-bodied function members (表達式方法體)
- using static (導入類靜態方法)
- Null-conditional operators (一元空值檢查操作符?.)
- String Interpolation (字符串插值)
- nameof Expressions (nameof 表達式)
- Index Initializers(索引初始化器)
- Exception Filters (異常過濾器)
- Await in Catch and Finally blocks (Catch,Finally語句塊中可用await)
- Extension Add methods in collection initializers (在集合初始化器中使用擴展的Add方法)
- Improved overload resolution (改進的重載解析)
- Auto-Property enhancements(自動屬性增強)
-
- C#7.0新特性
-
- out variables (out 變量)
- Tuples (元組)
- Discards (占位符)
- Pattern matching (模式匹配)
- Ref locals and returns (ref局部變量和返回ref變量)
- Local functions (本地方法)
- More expression-bodied members(更多的 表達式方法體 成員)
- Throw expressions (異常表達式)
- Generalized async return types(更泛化的異步返回類型)
- Numeric literal syntax improvements(數值字面量語法改進)
-
- C#7.1新特性
-
- Async main (異步Main方法)
- Default literal expressions (default字面量表達式)
- Inferred tuple element names(tuple元素名可推導)
- Reference assembly generation
-
- C#7.2
-
- Reference semantics with value types(只讀引用)
- Non-trailing named arguments(命名參數不需要在最后)
- Leading underscores in numeric literals(數字字面量的前導分隔符)
- private protected access modifier (private protected 訪問修飾符)
-
C#6.0新特性
Auto-Property enhancements(自動屬性增強)
Read-only auto-properties (真正的只讀屬性)
// 以前 // 只是限制了屬性在類外部只讀,而在類內部任何地方都可設置 public string Name { get; private set; } public void SetName(string name) { Name = name; } // c#6.0 // 1.通過只使用一個getter來聲明真正只讀 // 2.這樣的屬性,只能在構造器中初始化(含屬性聲明時),而類內部其他地方也不可再設置 public string Name { get; } = "小米喂大象"; // 允許 public User(string Name, string password, int age) { Name = name; // 允許 Password = password; Age = age; } public void SetName(string name) { Name = name; // 報錯 }
Auto-Property Initializers (自動屬性的初始化)
// 以前 // 需要屬性有setter,通過setter來初始化backing field public string Name { get; set; } public User(string name) { Name = "小米喂大象"; } // C#6.0 // 可以在屬性聲明的同時初始化 public string Name { get; } = "小米喂大象"; public int Age { get; set; } = 18;
Expression-bodied function members (表達式方法體)
// C#6.0 // 1. 類成員方法體是一句話的,都可以改成使用表達式,使用Lambda箭頭來表達 // 2. 只適用於只讀屬性和方法 public string Name => "小米喂大象"; // 只讀屬性 public void SetAge(int age) => Age = age; // 方法 public void Log(string msg) => System.Console.WriteLine($"{Name} : {msg}");
using static (導入類靜態方法)
// c#6.0 // 1.使用using static 語法,可以將一個類中的所有靜態方法導入到當前上下文, // 包括這個類中的嵌套類型,不包括實例方法,也不包括const字段 // 2.這樣引用這個類的方法,就可以直接引用,而不用再加前綴(形似C函數)。 using static System.Math; using static System.String; public Double Calc(int angle) { var tmp = Sin(angle) + Cos(ange); ... }
Null-conditional operators (一元空值檢查操作符?.)
// 以前 // 一個引用的null檢查和使用是分開的 User user; . . . if (user != null) { user.SetAge(19); } // C#6.0 // 1. 直接使用?.代替.操作符即可 // 2. ?.操作符確保其左邊表達式只計算一次 // 2. 如果引用是null,這直接返回類型匹配的null, 下面的name被推斷為string? user?.SetAge(19); var name = user?.Name;
String Interpolation (字符串插值)
// 以前 public override string ToString() { return string.Format("{0}:{1:D2}", Name, Age); } // C#6.0 // 1. 使用$開頭,花括號里直接放入表達式 // 2. 格式化字符串,可直接在花括號里表達式后面加上:,然后加上格式化字符串 // 3. 插值表達式里可以嵌套插值表達式 public override string ToString() { return $"{Name}:{Age:D2}"; }
nameof Expressions (nameof 表達式)
// C#6.0 // 1. nameof表達式返回一個變量、屬性或字段的名稱 // 2. 當需要一個符號的名稱時很有用,一可以避免手工打錯,二可以便於重構 // 3. 如果是一個限定了前綴的完整名稱,如nameof(User.Age), // nameof操作符也只是返回"Age",而不是"User.Age" public string Name { get => name; set { name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); } } public int Age { get => age; set { age = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(User.Age))); } }
Index Initializers(索引初始化器)
// c#6.0 // 允許使用[]操作符來初始化,這樣字典可以像其他序列容器一樣的語法初始化了 public Dictionary<string, User> Users = new Dictionary<string, User> { ['小米喂大象'] = new User(), ['Jack'] = new User(), }
Exception Filters (異常過濾器)
// C#6.0 // 1. catch字句后面可以帶一個 when表達式(早期是if,后被when替換) // 2. 如果when括號內表達式(下面代碼?部分)為真則Catch塊就執行,否則不執行 // 3. 利用這個表達式可以做很多事,包括過濾指定異常、調試、打印日志等。 try { } catch (Excepation e) when (?) { }
Await in Catch and Finally blocks (Catch,Finally語句塊中可用await)
// C#6.0 // 1. C#5.0中添加了async、await, 但是在哪里放await表達式,這個有一些限制 // 2. C#6.0中解決了這些限制中的其中一個,就是await可以放在catch、finally語句塊中了。 try { var result = await SomeTask; return result; } catch (Exception e) { await Log(e); } finally { await Cleanup(); }
Extension Add methods in collection initializers (在集合初始化器中使用擴展的Add方法)
// c#6.0 public class User { public string Name { get; set; } public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } } public class ActiveUsers : IEnumerable<User> { List<User> users = new List<User>(); //public void Add(User user) { // users.Add(user); //} public void Append(User user) { users.Add(user); } public IEnumerator<User> GetEnumerator() { throw new NotImplementedException(); } IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } } // 添加一個擴展的靜態方法Add public static class ActiveUsersExtensions { public static void Add(this ActiveUsers users, User u) => users.Append(u); } public class Test { // 1. 為了能夠像下面這樣使用集合初始化器,ActiveUsers必須擁有一個Add()方法 // 2. 現在,如果ActiveUsers 類只有一個Append()方法,沒有Add()方法,而你也 // 無法修改這個類,這時候下面的代碼就無法工作了。 // 3. C#6.0中允許你添加一個擴展的靜態方法Add來完成工作。 ActiveUsers users = new ActiveUsers { new User("xm01", 18), new User("xm02", 19), new User("xm03", 20), }; }
Improved overload resolution (改進的重載解析)
// C#6.0 // 1. 下面代碼,C#6.0以前,當編譯器看到Foo(Bar),會去匹配一個最合適的方法,但是, // 編譯器最終會報告失敗。因為編譯器在匹配函數簽名的時候,並沒有將函數返回值作為 // 一部分,所以編譯器不能明確該調用哪個Foo方法。 // 2. 同樣,C#6.0以前,編譯器不能區分Task.Run(Action)和Task.Run(Func<Task>()) // 3. C#6.0解決了此問題 int Bar() { return 1; } void Foo(Action f) { } void Foo(Func<int> f) { } void Main() { Foo(Bar); }
C#7.0新特性
out variables (out 變量)
// 以前 // out變量的聲明和初始化是分開的 int age; if (int.TryParse("18", out age)) { Console.WriteLine("age: " + age); } // C#7.0 // 1. C#7.0允許直接在方法的調用列表中聲明一個out變量 // 2. 這個變量可以聲明成var這種隱式類型 // 3. 這個變量的作用域范圍伸展到if語句塊的外部范圍 if (int.TryParse("18", out int age)) { Console.WriteLine("age: " + age); } if (int.TryParse("90", out var score)) { // 隱式類型也可以用 Console.WriteLine("score: " + score); } age += 1; // 這時候age仍然有效
Tuples (元組)
// c#7.0 // 1. C#7.0以前就已經有tuple, 但不是語言層面支持的,而且使用起來沒效率 // 2. C#7.0中使用tuple,需要引入 System.ValueTuple(如果平台不包含的話) // 3. 元組成員名可指定,不指定默認Item1,Item2,... // 4. 元組是值類型,其元素是公開字段,可修改 // 5. 元組中元素都相等,則元組相等 // 6. 元組可用於函數返回多個獨立變量,這樣不用定義一個struct或class // 7. 元組使用場合: // 元組成員名默認Item1, Item2 var name = ("Jack", "Ma"); (string, string) name1 = ("Jack", "Ma"); // 指定成員名稱為firstName, lastName (string firstName, string lastName) name2 = ("Jack", "Ma"); // 指定成員名稱為f, l var name3 = (f: "Jack", l: "Ma"); // 左右同時指定成員名稱,右邊的忽略 (string firstName, string lastName) name4 = (f: "Jack", l: "Ma"); // 返回一個元組 private (string FirstName, string LastName) GetName() { return ("Jack", "Ma"); } var name5 = GetName(); name5.FirstName = "Jack2"; name5.LastName = "Ma2"; // 析構元組成員到變量firstName, lastName (string firstName, string lastName) = GetName(); firstName = "Jack2"; lastName = "Ma2"; // 析構元組成員到變量f, l (string f, string l) = name5; f = "Jack2"; l = "Ma2"; // 析構元組成員到變量f2, l2 var (f2, l2) = name5;
Discards (占位符)
// C#7.0 // 1. 增加一個占位符_(下划線字符)來表示一個只寫的變量,這個變量只能寫,不能讀。 // 當想丟棄一個值的時候,可以使用。 // 2. 他不是實際變量,沒有實際存儲空間,所以可以多處使用。 // 3. 一般用於解構元組、調用帶out參數的方法、模式匹配,例如: // > 調用一個方法,這個方法帶有一個out參數,你根本不使用也不關心這個參數; // > 一個包含多個字段的元組,你只關心其中部分成員,不關心的成員可以使用占位符; // > 模式匹配中, _可以匹配任意表達式; // 4. 注意:_也是一個有效的變量標識符,在合理的情景下,_也會作為一個有效變量 private (string FirstName, string LastName) GetName() { return ("Jack", "Ma"); } private void GetName(out string FirstName, out string LastName) { FirstName = "Jack"; LastName = "Ma"; } // 只關心FirstName, LastName丟棄 var(firstName, _) = GetName(); GetName(out var firstName2, out _); // 有效變量_ public void Work(int _) { _ += 4; }
Pattern matching (模式匹配)
// C#7.0 // 模式匹配:匹配一個值是否具有某種特征(例如:是否是某個常量、某個類型、某個變量), // 如果是,順便可將這個值提取到對應特征的新變量中 // C#7.0中,利用已有的關鍵字is和switch來擴展,實現模式匹配 // 具有模式匹配的is表達式:不僅能匹配類型,還能匹配表達式 public static void TestIs(object o) { const string IP = "127.0.0.1"; // 匹配常量 if (o is IP) { Console.WriteLine("o is IP"); } if (o is null) { Console.WriteLine("o is null"); } // 匹配類型 if (o is float) { Console.WriteLine($"o is float"); } // 匹配類型,並提取值。檢測為true,這時候i會被明確賦值 if (o is int i) { Console.WriteLine($"o is int {i}"); } else { return; } // i仍然有效 // i變量稱為模式變量,和out變量一樣,統稱為表達式變量,作用域都擴展到了外圍 // 表達式變量的范圍擴展到了外圍,只有在前面的模式匹配為true是才有效 // 表達式變量為true時,才給變量明確賦值,這樣避免了模式不匹配時訪問這些變量 i++; Console.WriteLine($"i is {i}"); if (o is 4 * 4) { Console.WriteLine("o is 4*4"); } } // 可以模式匹配的switch // 1. 原來的switch限制為僅僅是string和數字類型的常量匹配,現在解除了 // 2. switch按照文本順序匹配,所以需要注意順序; // (原來switch的分支只匹配一個所以不需要順序;而現在可以匹配多個,行為變了) // 3. case子句后面可以帶模式匹配的表達式 // 4. default最后執行,也就是其他都不匹配時才執行,不管default語句放在什么位置。 // 5. 如沒有default分支,其他也不匹配,則不執行任何switch塊代碼,直接執行其后面代碼 // 6. case后帶var形式變量的匹配,近似於default // 7. case 子句引入的模式變量只在switch塊內有效 public static void TestSwitch(object o) { switch (o) { case "127.0.0.1": Console.WriteLine("o is IP"); break; case float f: Console.WriteLine($"o is float {f}"); break; case int i when i == 4: Console.WriteLine($"o is int {i} == 4"); break; case int i: Console.WriteLine($"o is int {i}"); break; case string s when s.Contains("127"): Console.WriteLine("o is string, contains 127 "); break; case string s when s.Contains("abc"): Console.WriteLine("o is string, contains 127 "); break; case var a when a.ToString().Length == 0: Console.WriteLine($"{a} : a.ToString().Length == 0"); break; case null: Console.WriteLine($"o is null"); break; default: Console.WriteLine("default"); break; } }
Ref locals and returns (ref局部變量和返回ref變量)
// C#7.0 // C#7.0以前的ref只能用於函數參數,現在可以用於本地引用和作為引用返回 // 1. 需要添加關鍵字ref,定義引用時需要,返回引用時也需要 // 2. 引用聲明和初始化必須在一起,不能拆分 // 3. 引用一旦聲明,就不可修改,不可重新再定向 // 4. 函數無法返回超越其作用域的引用 // 需要添加關鍵字ref,表示函數返回一個ref int public static ref int GetLast(int[] a) { if (a == null || a.Length < 1) { throw new Exception(""); } int number = 18; // 錯誤聲明: 引用申明和初始化分開是錯誤的 //ref int n1; //n1 = number; // 正確聲明: 申明引用時必須初始化,聲明和初始化在一起 // 添加關鍵字ref表示n1是一個引用, ref int n1 = ref number; // n1指向number,不論修改n1或number,對雙方都有影響,相當於雙方綁定了。 n1 = 19; Console.WriteLine($"n1:{n1}, number:{number}"); number = 20; Console.WriteLine($"n1:{n1}, number:{number}"); // 語法錯誤,引用不可被重定向到另一個引用 //n1 = ref a[2]; // 語法正確,但本質是將a[2]的值賦值給n1引用所指,n1仍指向number n1 = a[2]; Console.WriteLine($"n1:{n1}, number:{number}, a[2]:{a[2]}"); number = 21; Console.WriteLine($"n1:{n1}, number:{number}, a[2]:{a[2]}"); // --------------------- 引用返回 ------------------------ // 錯誤:n1引用number,但number生存期限於方法內,故不可返回 // return ref n1; // 正確:n2引用a[2],a[2]生存期不僅僅限於方法內,所以可以返回。 ref int n2 = ref a[a.Length-1]; return ref n2; // 需要ref返回一個引用 return ref a[a.Length-1]; // 也可以直接返回一個引用 } public static void Main(string[] args) { int[] a = { 0, 1, 2, 3, 4, 5}; // x不是一個引用,函數將值賦值給左側變量x int x = GetLast(a); Console.WriteLine($"x:{x}, a[2]:{a[a.Length-1]}"); x = 99; Console.WriteLine($"x:{x}, a[2]:{a[a.Length-1]} \n"); // 返回引用,需要使用ref關鍵字,y是一個引用,指向a[a.Lenght-1] ref int y = ref GetLast(a); Console.WriteLine($"y:{y}, a[2]:{a[a.Length-1]}"); y = 100; Console.WriteLine($"y:{y}, a[2]:{a[a.Length-1]}"); Console.ReadKey(); }
Local functions (本地方法)
// C#7.0 // 1. 定義在一個方法體內的函數,稱為本地方法 // 2. 本地方法只在其外部方法體內有效 // 3. 本地方法可定義在其外部方法的任何地方 // 4. 外部方法其作用域內參數或變量,都可用於本地方法 // 5. 本地方法實質被編譯為當前類的一個私有成員方法, // 但被語言層級限制為只能在其外部方法內使用 // 6. 由於(5),所以本地方法和類方法一樣,沒有特殊限制,異步、泛型、Lambda等都可用 // 7. 常見用例:給迭代器方法和異步方法提供參數檢查,因為這兩類方法報告錯誤比較晚 public static IEnumerable<int> SubsetOfIntArray(int start, int end) { if (start < end) { throw new ArgumentException($"start({start}) < end({end})"); } return Subset(); IEnumerable<int> Subset() { for (var i = start; i < end; i++) yield return i; } }
More expression-bodied members(更多的 表達式方法體 成員)
// C#7.0 // 1. 類成員方法體是一句話的,都可以改成使用表達式,使用Lambda箭頭來表達 // 2. C#6.0中,這種寫法只適用於只讀屬性和方法 // 3. C#7.0中,這種寫法可以用於更多類成員,包括構造函數、析構函數、屬性訪問器等 public string Name => "小米喂大象"; //只讀屬性 public void SetAge(int age) => Age = age; //方法 public void Log(string msg) => System.Console.WriteLine($"{Name} : {msg}"); public void Init(string name, string password, int age) { Name = name; Password = password; Age = age; } public User(string name) => Init(name, "", 18); //構造函數 public User(string name, string password) => Init(name, password, 18); public ~User() => System.Console.WriteLine("Finalized"); //析構函數(僅示例) public string password; public int Password{ //屬性訪問器 get => password; set => SetPassword(value); }
Throw expressions (異常表達式)
// C#7.0 // 1. c#7.0以前,throw是一個語句,因為是語句,所以在某些場合不能使用。 // 包括條件表達式、空合並表達式、Lambda表達式等。 // 2. C#7.0可以使用了, 語法不變,但是可以放置在更多的位置 public string Name { get => name; set => name = value ?? throw new ArgumentNullException("Name must not be null"); }
Generalized async return types(更泛化的異步返回類型)
// C#7.0 // 1. 7.0以前異步方法只能返回void、Task、Task<T>,現在允許定義其他類型來返回 // 2. 主要使用情景:從異步方法返回Task引用,需要分配對象,某些情況下會導致性能問題。 // 遇到對性能敏感問題的時候,可以考慮使用ValueTask<T>替換Task<T>。
Numeric literal syntax improvements(數值字面量語法改進)
// C#7.0 // c#7.0為了增加數字的可讀性,增加了兩個新特性:二進制字面量(ob開頭)、數字分隔符(_) int b = 123_456_789; // 作為千單位分隔符 int c = 0b10101010; // 增加了表示二進制的字面量, 以0b開頭 int d = 0b1011_1010; // 二進制字面量里加入數字分割符號_ float e = 3.1415_926f; // 其他包括float、double、decimal同樣可以使用 double f = 1.345_234_322_333_567_222_777d; decimal g = 1.618_033_988_749_894_586_834_365_638_117_720_309_179M; long h = 0xff_42_90_b8; // 在十六進制中使用
C#7.1新特性
C#7.1是c#的第一個帶小數點的版本,意味着快速迭代與發布
一般需要在編譯器里設置語言版本才能使用
Async main (異步Main方法)
// C#7.0中async不能用於main方法,7.1可以 // 以前 static int Main() { return DoAsyncWork().GetAwaiter().GetResult(); } // 現在 static async Task<int> Main() { return await DoAsyncWork(); } static async Task Main() { // 沒有返回 await SomeAsyncMethod(); }
Default literal expressions (default字面量表達式)
// 1. C#7.1以前給一個變量設置缺省值,需要使用default(T), C#7.1因為可以推斷表達式 // 的類型,所以可以直接使用default字面量,編譯器推斷出與default(T)一樣的值 // 2. default字面量可用於以下任意位置: // 變量初始值設定項 // 變量賦值 // 聲明可選參數的默認值 // 為方法調用參數提供值 // 返回語句 // expression in an expression bodied member // (使用表達式方法體的成員中的表達式) public class User { public string Name { get; set; } = default; public int Age { get; set; } = default; public int Score => default; } public static int Test(string name, int age, int score = default) { // 以前 string s1 = default(string); var s2 = default(string); int i = default(int); User u = default(User); // 現在 string s3 = default; string s4 = "hello"; s4 = default; return default; } Test(default, default);
Inferred tuple element names(tuple元素名可推導)
// c#7.0引入tuple,7.1增強了tuple中元素的命名,可通過推導來完成tuple中元素的命名 // 使用變量來初始化tuple時,可以使用變量名給tuple中元素命名 var name = "xm01"; var age = 18; var p = (name:name, age:18); // 以前,顯式命名 var p2 = (name, age); // 現在,可以推導來命名 p2.age = 19;
Reference assembly generation
編譯器添加了兩個新的選項用於控制引用程序集的生成:-refout 和 -refonly
一個表示只引用,一個表示需要輸出引用(故需要指定路徑)
C#7.2
Reference semantics with value types(只讀引用)
- 使用值類型變量時,通過可避免堆分配,但需要進行一次復制操作;
- 為了取得折中效果, C#7.2提供了一個機制:似值類型不可被修改,但按引用傳遞。
// 這幾種情況可以: // 1. in修飾符修飾的參數,不可被調用的方法修改; // 2. ref readonly方式返回一個值,不能被修改; // 3. readonly struct聲明一個結構,可以作為in參數傳遞 // 4. ref struct 聲明一個結構,指示直接訪問托管內存,始終分配有堆棧。
Non-trailing named arguments(命名參數不需要在最后)
- C#7.2以前,命名參數后面不能再跟位置參數
- C#7.2以后,只要命名參數位置正確,可以和位置參數混用
- 這樣做的目的是: 使用名字參數的調用,一眼可以看出來這個參數的含義,
例如參數是一個boolean型的參數,寫代碼時直接傳true,根本看不出什么含義,這時候寫上名字可以明確調用接口
Leading underscores in numeric literals(數字字面量的前導分隔符)
// 1. C#7.0中提供了下划線來分割數字字面量,以提高可讀性, // 但是 下划線分割符(_) 不可作為字面量的第一個字符。 // 2. c#7.2中允許十六進制字面量和二進制字面量以_開頭 // 以前 int x = 0b1011_1010; long y = 0xff_42_90_b8; // 現在 int x = 0b_1011_1010; long y = 0x_ff_42_90_b8;
private protected access modifier (private protected 訪問修飾符)
- C#7.2添加了一個private protected 訪問修飾符
- 表示:
1)只有自己訪問;
2)派生類也可以訪問,但僅限於在同一個程序集的派生類
原文鏈接:https://blog.csdn.net/wsh31415926/article/details/79907545