# CSharp (1.0-7.0)新特性

作者:胡飞玲 时间: 2020-10-16

# 第一章 概述

img

CSharp 语言规范 GitHub 库参见:https://github.com/dotnet/csharplang

CSharp 语言路线图及开发中的特性参见: [https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md](https://github.com/dotnet/roslyn/blob/master/docs/Language Feature Status.md)

# 第二章 CSharp 7.0 新特性

# 1. out 变量(out variables)

*旧版:*我们使用 out 变量的时候,需要在外部先申明,然后才能传入方法,类似如下:

string message = "";

bool result = AddData(out message);
1
2
3

新版:

bool result = AddData(out string message);
1

# 2. Tuples(元组)

用途:更优雅地返回多个值

适用一个函数多个返回值,需要通过 nuget 引用 System.ValueTuple 示例如下:

private void TuplesExample()
{
    //1、var匿名来获取
    var data = GetData();
    Console.WriteLine(data.result);
    Console.WriteLine(data.data.Count);
    Console.WriteLine(data.message);
    //2、var匿名来获取
    var (result1,data1,message1) = GetData();
    // 3、不用var匿名来获取
    (bool result2, ArrayList data2, string message2) = GetData();
}
//方法定义为多个返回值,并命名
private (bool result, ArrayList data, string message) GetData()
{
    bool result = true;
    string info = "成功";
    ArrayList pList = new ArrayList();
    return (result, pList, info);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3. 模式匹配(Pattern matching)

需要通过 nuget 引用 System.ValueTuple

  1. is 表达式(is expressions)

使用方法: 条件控制语句(obj is type variable){}

private int PatternMatchingExample(List<object> values)
{
    int sum = 0;
    //is 表达式
    values.ForEach(obj => {
        if (obj is short num)
            sum += num;
        else if (obj is string str && int.TryParse(str, out int result))
            sum += result;
        else if (obj is List<object> list)
            sum += PatternMatchingExample(list);
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. switch 语句更新(switch statement updates)

使用方法:case type variable:

private int PatternMatchingExample(List<object> values)
{
   int sum = 0;
   values.ForEach(obj =>
                  {
                      switch (obj)
                      {
                          case short num:
                              sum += num;
                              break;
                          case string str when int.TryParse(str, out int result):
                              sum += result;
                              break;
                          case List<object> list:
                              sum += PatternMatchingExample(list);
                              break;
                      }
                  });
   return sum;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 4. 局部引用和引用返回 (Ref locals and returns)

不必担心值类型的引用传递:CSharp7.0 以前的 ref 只能用于函数参数,现在可以用于本地引用和作为引用返回

用法:

  1. 需要添加关键字 ref,定义引用时需要,返回引用时也需要

  2. 引用声明和初始化必须在一起,不能拆分

  3. 引用一旦声明,就不可修改,不可重新再定向

  4. 函数无法返回超越其作用域的引用

	private void RefLocalExample()
    {

      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]}");

    }

    public static ref int GetLast(int[] a)

    {

      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];  // 也可以直接返回一个引用

    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

# 5. 局部函数(Local functions)

函数作用域新玩法:函数里定义函数了,通常这个函数里的函数只能在外层函数里访问

	private void LocalFunctionsExample()
    {
      int num = OperData(100);

      int OperData(int data)
      {
        return data * 10;
      }
    }
1
2
3
4
5
6
7
8
9

# 6. 更多的表达式体成员(More expression-bodied members)

// CSharp7.0

// 1. 类成员方法体是一句话的,都可以改成使用表达式,使用 Lambda 箭头来表达

// 2. CSharp6.0 中,这种写法只适用于只读属性和方法

// 3. CSharp7.0 中,这种写法可以用于更多类成员,包括构造函数、析构函数、属性访问器等

public class User
{

   public string Name { get; set; }

   public int Age { get; set; }

   public void SetAge(int age) => Age = age; //方法

   public string password;

   public string Password

   { //属性访问器

     get => password;

     set => password = value;

   }

   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);

 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 7. Throw 表达式(Throw expressions)

新增的运算符??(两个半角问号)用于在引用为 null 的时候抛出异常:

// 1. CSharp7.0 以前,throw 是一个语句,因为是语句,所以在某些场合不能使用。

// 包括如:条件表达式(? :)、null 合并运算符(??)、一些 Lambda

// 2. CSharp7.0 可以使用了, 语法不变,但是可以放置在更多的位置

private string name;

public string Name
{
    get => name;
    set => name = value ??
        throw new ArgumentNullException("Name must not be null");
}
1
2
3
4
5
6
7
8

# 8. 扩展异步返回类型(Generalized async return types)

// CSharp7.0

// 1. 7.0 以前异步方法只能返回 void、Task、Task<T>,现在允许定义其他类型来返回

// 2. 主要使用情景:从异步方法返回 Task 引用,需要分配对象,某些情况下会导致性能问题。

// 遇到对性能敏感问题的时候,可以考虑使用 ValueTask<T>替换 Task<T>。

public class CaCheContext

 {

   public ValueTask<int> CachedFunc()

   {

     return (cache) ? new ValueTask<int>(cacheResult) :

new ValueTask<int>(loadCache());

   }

   private bool cache = false;

   private int cacheResult;

   private async Task<int> loadCache()

   {

     await Task.Delay(5000);

     cache = true;

     cacheResult = 100;

     return cacheResult;

   }

 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 9. 数字文本语法的改进(Numeric literal syntax improvements)

 	private void NumericExample()

    {

      // CSharp7.0

      // CSharp7.0增加了两个新特性:二进制字面量(ob开头)、数字分隔符(_)

      int b = 123_456_789; // 作为千单位分隔符

      float e = 3.1415_926f; // 其他包括float、double、decimal同样可以使用

      long h = 0xff_42_90_b8; // 在十六进制中使用

      int c = 0b10101010; // 增加了表示二进制的字面量, 以0b开头

      int d = 0b1011_1010; // 二进制字面量里加入数字分割符号_

    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 第二章 CSharp 6.0 新特性

# 1. 自动属性初始化

public class Person
{
    public string Name { get; set; } = "张三";

    public int Age { get; set; } = 20;

}
1
2
3
4
5
6
7

# 2. 字符串嵌入值

以前版本,字符串格式化用要使用 string.Format,在 CSharp6.0 中,可以直接使用$进行字符串的嵌入

private void StringFormatExample()
{
   //旧版
   Console.WriteLine(string.Format("当前时间:{0}", DateTime.Now.ToString()));
   //新特性
   Console.WriteLine($"当前时间:{DateTime.Now.ToString()}");
}
1
2
3
4
5
6
7

# 3. 导入静态类

1)使用 using 导入静态类

2)导入静态类以后,可以像使用普通方法一样,直接使用 如:

img
private void ImportStaticExample()
{
    Console.WriteLine($"文件是否存在:{Exists("c:\test.txt")}");
}
1
2
3
4

# 4. 空值运算符

在 CSharp6.0 中有了空值运算符 ? ,对象为空时,访问属性也不出现“未将对象引用设置到对象实例”

private void NullExample()
{
   Person person = null;
   string name = person ?.Name;
}
1
2
3
4
5

# 5. 对象初始化器

在 CSharp6.0 中,可以通过索引的方式给字段进行初始化

	private void InitExample()
	{
    	IDictionary<string, string> dictNew = new Dictionary<string, string>()

      {

        ["一"] = "first",

        ["二"] = "second"

      };

    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6. 异常过滤器

CSharp6.0 中,可以设置在满足某种条件的情况下,才能进入异常捕获:

private void ExceptionExample()
{
    int num = 1;
    try
    {
        while (true)
            num += 1;
    }
    catch(Exception ex) when(num>10)
    {
        Console.WriteLine("num >10 ");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 7. nameof 表达式

类似 typeof,应用场景:如打印日志,或反射时通过属性名称获取对应值等等

private void NameofExample()
{
    string personName = "李四";
    Console.WriteLine($"{nameof(personName)}:{personName}");
    //打印结果:personName:李四
}
1
2
3
4
5
6

# 8. 在属性/方法里面使用 Lambda 表达式

img

# 第三章 CSharp 5.0 新特性

# 1. 绑定运算符 :=:

comboBox1.Text :=: this.User.Name;

# 2. 带参数的泛型构造函数

这个的加入给一些设计增加了强大功能,泛型早在 CSharp2.0 加入后就有着强大的应用,一般稍微设计比较好的框架,都会用到泛型,CSharp5.0 加入带参数泛型构造函数,则在原有基础上对 CSharp 泛型完善了很多。

img

# 3. 支持 null 类型运算

int? x = null;

int? y = x + 40;

Console.WriteLine(y);//结果:40,不会报错
1
2
3
4
5

# 4. Async 和 Await

通过 async 和 await 两个关键字,引入了一种新的基于任务的异步编程模型(TAP)。在这种方式下,可以通过类似同步方式编写异步代码,极大简化了异步编程模型

private async void AsyncExample()
{
    var webClient = new WebClient();
    var result = await webClient.DownloadStringTaskAsync("www.baidu.com");
    Console.WriteLine(result);
}
1
2
3
4
5
6

# 第四章 CSharp 4.0 新特性

# 1. 动态绑定(Dynamic binding)

private void ObjectInitExample()
{
	dynamic dyn = 1.234;
	Console.WriteLine(dyn.GetType());

	dyn = "ojlovecd";
	Console.WriteLine(dyn.GetType());

	//在运行时dyn会知道自己是个什么类型。
	//这里的缺点在于编译的时候无法检查方法的合法性,写错的话就会出运行时错误。
	dyn = new User { Name = "", Age = 20 };
	Console.WriteLine(dyn.Name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. 命名参数和可选参数(Named and optional arguments)

private void OptionalArgumentsExample()
{
    //可选参数
    OperData("测试");
    OperData("测试",100);

    //命名参数让我们可以在调用方法时指定参数名字来给参数赋值,可以忽略参数的顺序
    OperData(data: 100, message: "测试");
}

private void OperData(string message,int data=10)
{
    Console.WriteLine(data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3. 泛型的协变和逆变(Generic co- and contravariance)

# 4. 开启嵌入类型信息(Embedded interop types (“NoPIA”)

# 5. 一些新增方便的功能

System.Numerics.Complex 复数

System.Numerics.BigInteger 大数

System.Tuple 对象

Directory.EnumerateDirectories

Directory.EnumerateFiles

Directory.EnumerateFileSystemEntries

String.IsNullOrWhiteSpace()
1
2
3
4
5
6
7
8
9
10
11
12
13

# 第五章 CSharp 3.0 新特性

# 1. Linq

Linq 主要包含 4 个组件——Linq to Objects、Linq to XML、Linq to DataSet 和 Linq to SQL,这 4 个组件对应 4 种查询的对象:内存集合中的对象,XML,SQL 和 Dataset

# 2. 对象和集合初始化器(Object and collection initializers)

User user = new User { Name = "AA", Age = 22 };
List<User> userList = new List<User>{
    new User{Name="AA",Age=22},
    new User{Name="BB",Age=25},
};
1
2
3
4
5

# 3. 自动属性,自动生成属性方法(Auto-Implemented properties)

public Class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}
1
2
3
4
5

# 4. 匿名类型(Anonymous types)

//使用一个临时的对象,不用定义

//往往这样的对象只需要使用在局部的场合,使用以后就不再使用了,

//例如从数据库中查询出来的临时结果

private void AnonymousExample()
{
    //创建的people的Name和Age是只读的,不能去修改
    var people = new { Name = "AA", Age = 10 };
    Console.WriteLine(people.Name);
}
1
2
3
4
5
6

# 5. 扩展方法(Extension methods)

# 6. 查询表达式(Query expressions)

这个其实就是扩展方法的运用,编译器提供了相关的语法便利,下面两端代码是等价的:

from g in

from c in customers

group c by c.Country

select new { Country = g.Key, CustCount = g.Count() }


customers.

GroupBy(c => c.Country).

Select(g => new { Country = g.Key, CustCount = g.Count() })
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7. Lambda 表达式(Lambda expression)

# 8.表达式树(Expression trees)

# 9. 部分方法(Partial methods)

# 第六章 CSharp 2.0 新特性

# 1. 泛型(Generics)

# 2. 分部类型(Partial types)

# 3. 匿名方法(Anonymous methods)

# 4. 迭代器(Iterators)

# 5. Null 的类型(Nullable types)

# 6. 属性访问控制(Getter/setter separate accessibility)

# 7. 方法组转换(Method group conversions (delegates))

# 8. 委托、接口的协变和逆变

# 9. 静态类(Static classes)

# 10. 委托推断,允许将方法名直接赋给委托变量(Delegate inference)

# 第七章 CSharp 1.0 新特性

# 1. 面向对象特性,支持类类型(Classes)

# 2. 结构(Structs)

# 3. 接口(Interfaces)

# 4. 事件(Events)

# 5. 属性,类的成员(Properties)

# 6. 委托(Delegates)

# 7. 表达式、语句、操作符(Expressions,Statements,Operators)

# 8. 特性(Attributes)

# 9. 字面值(Literals)