1. 程式人生 > >C#6.0,C#7.0新特性

C#6.0,C#7.0新特性

 

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 (改進的過載解析)
  • 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(只讀引用)

  1. 使用值型別變數時,通過可避免堆分配,但需要進行一次複製操作;
  2. 為了取得折中效果, C#7.2提供了一個機制:似值型別不可被修改,但按引用傳遞。
// 這幾種情況可以:
// 1. in修飾符修飾的引數,不可被呼叫的方法修改;
// 2. ref readonly方式返回一個值,不能被修改;
// 3. readonly struct宣告一個結構,可以作為in引數傳遞
// 4. ref struct 宣告一個結構,指示直接訪問託管記憶體,始終分配有堆疊。

 

Non-trailing named arguments(命名引數不需要在最後)

  1. C#7.2以前,命名引數後面不能再跟位置引數
  2. C#7.2以後,只要命名引數位置正確,可以和位置引數混用
  3. 這樣做的目的是: 使用名字引數的呼叫,一眼可以看出來這個引數的含義, 
    例如引數是一個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 訪問修飾符)

  1. C#7.2添加了一個private protected 訪問修飾符
  2. 表示: 
    1)只有自己訪問; 
    2)派生類也可以訪問,但僅限於在同一個程式集的派生類

原文連結:https://blog.csdn.net/wsh31415926/article/details/79907545