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]}
");
// 返回引用,需要使用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