C#6.0,C#7.0新特性
阿新 • • 發佈:2018-11-10
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