C#各个版本的新特性

作者 Zhendong Ho 日期 2019-09-09
C#
C#各个版本的新特性

C# 7.0 新特性

C# 7.0 随 Visual Studio 2017 发布。

数字字面量的改进

C# 7 中,数字字面量可以使用下划线来改善可读性、它们称为数字分隔符而被编译器忽略

int million = 1_000_000;

二进制字面量可以使用0b前缀进行标识。

var b = 0b1010_1011_1100_1101_1110_1111;

输出变量及参数忽略

C# 7 中,调用含有out参数的方法将更加容易。

首先,可以直接声明输出变量(在这之前不允许,将无法通过编译)。

bool successful = int.TryParse("123", out int result);	// 直接声明输出变量
Console.WriteLine(result);

当调用含有多个out参数的方法时,可以使用下划线字符忽略不关心的参数

SomeBigMethod(out _, out _, out _, out int x, out _, out _, out _,);	// 只需要用到x参数
Console.WriteLine(x);

模式

is运算符可以引入变量,称为模式变量

void Foo (object x)
{
if (x is string s)
{
Console.WriteLine(s.Length);
}
}

switch语句也支持模式,因此可以选择常量还有类型。另外可以使用when子句来指定一个判断条件,或者直接选择null

switch (x)
{
case int i:
Console.WriteLine("It's an int!");
break;
case string s:
Console.WriteLine(s.Length); // we can use the s variable
break;
case bool b when b == true: // Matches only when b is true
Console.WriteLine("True");
break;
case null: // 直接选择null
Console.WriteLine("Nothing");
break;
}

is运算符和模式变量

从 C# 7 开始,可以在使用is运算符的同时引入一个变量

if (a is Stock s)
{
Console.WriteLine(s.SharesOwned);
}

上面的代码等价于下面的代码:

Stock s;
if (a is Stock)
{
s = (Stock)a;
Console.WriteLine(s.SharesOwned);
}

引入的变量可以“立即”使用。

if (a is Stock s && s.SharesOwned > 100000)
{
Console.WriteLine("Wealthy");
}

引入的变量在is表达式之外仍然在作用域内

if (a is Stock s && s.SharesOwned > 100000)
{
Console.WriteLine("Wealthy");
}
else
{
s = new Stock(); // s is in scope
}
Console.WriteLine(s.SharesOwned); // still in scope

局部方法

局部方法是声明在其他函数内部的方法。

void WriteCubes()
{
Console.WriteLine(Cube(3));
Console.WriteLine(Cube(4));
Console.WriteLine(Cube(5));

int Cube(int value) => value * value * value; // 局部方法
}

局部方法仅仅在其包含函数内可见,它们可以像Lambda表达式那样捕获局部变量

更多的表达式体成员

C# 6 引入了以“胖箭头”语法表示的表达式体方法只读属性运算符以及索引器。而 C# 7 更将其扩展到了构造函数读写属性终结器中。

public class Person
{
string name;

public Person(string name) => Name = name; // 构造函数

public string Name // 属性
{
get => name;
set => name = value ?? "";
}

~Person() => Console.WriteLine("finalize"); // 终结器
}

解构器

C# 7 引入了解构器模式。构造器一般接受一系列值作为参数,并将其赋值给字段。而解构器则正相反它将字段反向赋值给变量

public void Deconstruct(out string firstName, out string lastName)
{
int spacePos = name.IndexOf(' ');
firstName = name.Substring(0, spacePos);
lastName = name.Substring(spacePos + 1);
}

解构器以特定的语法进行调用:

var joe = new Person("Joe Bloggs");
var (first, last) = joe; // Deconstruction
Console.WriteLine(first); // Joe
Console.WriteLine(last); // Bloggs

元组

C# 7 支持显式的元组。元组提供了一种存储一系列相关值的简单方式。

var bob = ("Bob", 23);
Console.WriteLine(bob.Item1); // Bob
Console.WriteLine(bob.Item2); // 23

C#的新元组实质上是使用System.ValueTuple<T>泛型结构的语法糖。此外还可以对元组的元素进行命名

var tuple = (Name: "Bob", Age: 23);
Console.WriteLine(tuple.Name); // Bob
Console.WriteLine(tuple.Age); // 23

元组可以替代函数中通过一系列out参数返回多个值的写法。

static (int, int) GetFilePosition() => (3, 10);	// 返回值为两个int类型的元组

static void Main()
{
var pos = GetFilePosition();
Console.WriteLine(pos.row); // 3
Console.WriteLine(pos.column); // 10
}

元组隐式地支持解构模式,因此因容易结构为若干独立的变量

static (int, int) GetFilePosition() => (3, 10);

static void Main()
{
(int row, int column) = GetFilePosition(); // Creates 2 local variables
Console.WriteLine(row); // 3
Console.WriteLine(column); // 10
}

throw表达式

在 C# 7 之前,throw一直是一个语句。现在,它也可以作为表达式出现在表达式体函数中。

public string Foo() => throw new NotImplementedException();

throw表达式也可以出现在三目判断运算符中。

string Capitalize(string value) =>
value == null ? throw new ArgumentException() :
char.ToUpper(value[0]) + value.Substring(1);

C# 6.0 新特性

C# 6.0 随 Visual Studio 2015 发布。C# 6.0 采用了完全使用C#编写的编译器,即“Roslyn”项目。

null条件运算符

null条件(Elvis)运算符可以避免在调用方法或访问类型的成员之前,显式地编写用于null判断的语句

// result将会为null而不会抛出NullReferenceException。
System.Text.StringBuilder sb = null;
string result = sb?.ToString(); // result is null

表达式体函数

表达式体函数(expression-bodied function),可以以Lambda表达式的形式书写仅仅包含一个表达式方法属性运算符以及索引器使代码更加简短

public int TimesTwo(int x) => x * 2;
public string SomeProperty => "Property value";

属性初始化器

属性初始化器(property initializer)可以对自动属性进行初始赋值

public DateTime TimeCreated { get; set; } = DateTime.Now;

这种初始化也支持只读属性

public DateTime TimeCreate { get; } = DateTime.Now;

另外,只读属性也可以在构造器中进行赋值。

索引初始化器

索引初始化器(index initializer)可以一次性初始化具有索引器的任意类型。

var dict = new Dictionary<int, string>()
{
[3] = "three",
[10] = "ten"
};

字符串插值

字符串插值(string interploation)用更加简单的方式替代了string.Format

string s = $"It is {DateTime.Now.DayOfWeek} today";

异常过滤器

异常过滤器(exception fliters)可以在catch块上再添加一个条件

string html;
try
{
html = new WebClient().DownloadString("http://asef");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
// ...
}

using static指令

using static指令可以引入一个类型的所有静态成员,这样就可以不用书写类型而直接使用这些成员

using static System.Console;

static void Main()
{
WriteLine("Hello world"); // WriteLine instead of Console.WriteLine
}

nameof运算符

nameof运算符返回变量类型或者其他符号的名称。这样在Visual Studio中可以避免变量重命名造成不一致的代码。

int capacity = 123;
string x = nameof(capacity); // x is "capacity"
string y = nameof(Uri.Host); // y is "Host"

其他

  • C# 6.0 可以在catchfinally块中使用await

C# 5.0 新特性

异步编程

C# 5.0 最大的新特性是通过两个关键字,asyncawait,支持异步功能。异步功能支持异步延续,从而简化响应式和线程安全的富客户端应用程序的编写。它还有利于编写高并发和高效的I/O密集型应用程序,而不需要为每一个操作绑定一个线程资源。

C# 4.0 新特性

C# 4.0 增加的新特性有:

  • 动态绑定
  • 可选参数和命名参数
  • 用泛型接口和委托实现类型变化
  • 改进COM互操作性

动态绑定

动态绑定将绑定过程(解析类型与成员的过程)从编译时推迟到运行时。这种方法适用于一些需要避免使用复杂反射代码的场合。动态绑定还适用于实现动态语言以及COM组件的互操作

可选参数

可选参数允许函数指定参数的默认值,这样调用者就可以省略一些参数,而命名参数则允许函数的调用者按名字而非按位置指定参数

协变和逆变

类型变化规则在 C# 4.0 进行了一定程度的放宽,因此泛型接口泛型委托类型参数可以标记为协变(convariant)逆变(contravariant),从而支持更加自然的类型转换。

COM互操作性

COM互操作性在 C# 4.0 中进行了三个方面的改进。

  1. 参数可以通过引用传递,并无须使用ref关键字(特别适用于与可选参数一同使用)。
  2. 包含COM互操作(interop)类型的程序集可以链接而无须引用。链接的互操作类型支持类型相等转换,无须使用主互操作程序集(Primary Interop Assembly),并且解决了版本控制和部署的难题。
  3. 链接的互操作类型中的函数若返回COM变体类型,则会映射为dynamic而不是object,因此无须进行强制类型转换。

C# 3.0 新特性

LINQ

C# 3.0 增加的特性主要集中在语言集成查询(Language Integrated Query,LINQ)上。LINQ令 C# 程序可以直接编写查询并以静态方式检查其正确性。它可以查询本地集合(如列表或XML文档),也可以查询远程数据源(如数据库)

隐式类型局部变量

var关键字允许在声明语句中省略变量类型,然后由编译器推断其类型。这样可以简化代码并支持匿名类型。匿名类型是一些即时创建的类,它们常用于生成LINQ查询的最终输出结果。数组也可以隐式类型化。

对象初始化器

对象初始化器允许在调用构造器之后以内联的方式设置属性,从而简化对象的构造过程。对象初始化器不仅支持命名类型也支持匿名类型

Lambda表达式

Lambda表达式是由编译器即时创建的微型函数,适用于创建流畅的LINQ查询。

扩展方法

扩展方法可以在不修改类型定义的情况下使用新的方法扩展现有类型,使静态方法变得像实例方法一样。LINQ表达式的查询运算符就是使用扩展方法实现的

查询表达式

查询表达式提供了编写LINQ查询的更高级语法,大大简化了具有多个序列或范围变量的LINQ查询的编写过程。

表达式树

表达式树是赋值给一种特殊类型Expression<TDelegate>的Lambda表达式的DOM(文档对象模型)。表达式树使LINQ查询能够远程执行(例如在数据库服务器上),因为它们可以在运行时进行转换和翻译(例如变成SQL语句)。

其他

  • C# 3.0 还添加了自动化属性分部方法(Partial Method)

C# 2.0 新特性

C# 2 提供的新特性包括泛型可空类型(nullable type)迭代器以及匿名方法(Lambda表达式的前身)。这些新特性为 C# 3 引入LINQ铺平了道路。

C# 2 还添加了分部类静态类以及许多细节功能,例如对命名空间别名友元程序集定长缓冲区的支持。

泛型需要在运行时仍能够确保类型的正确性,因此需要引入新的CLR(CLR 2.0)才能达成该目标。