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

posted @ 2019-03-22 17:07  Mr.石  阅读(612)  评论(0编辑  收藏  举报