C# · #csharp#dotnet#language-features

C# 6.0 新特性解析:让代码更简洁

2016.03.25 C# 3 min 1.3k
// 目录 · contents

VS 2015 发布后,C# 6.0 随之而来。这个版本没有大的架构性改变(async/await 是 5.0 的事),主要是一批”语法糖”——让常见模式写起来更简洁。升级项目之后我把这些新特性挨个试了一遍,记录哪些真的有用、哪些只是噱头。

1. Null 条件运算符 ?.

这个是 6.0 里我最喜欢的特性,彻底消灭了大量防御性 null 检查代码:

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
// 旧写法
string city = null;
if (user != null && user.Address != null)
{
city = user.Address.City;
}

// C# 6.0
string city = user?.Address?.City; // user 或 Address 为 null 时返回 null

// 结合 null 合并运算符
string city = user?.Address?.City ?? "未知城市";

// 方法调用
int? length = user?.Name?.Length;

// 集合索引
string first = list?[0]; // list 为 null 时返回 null,不抛异常

// 事件触发(线程安全)
// 旧写法(不安全,存在竞态条件)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));

// 新写法(安全)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

2. 字符串插值 $""

告别 string.Format 的索引地狱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
string name = "Alice";
int age = 30;

// 旧写法
string msg1 = string.Format("用户 {0},年龄 {1}", name, age);

// C# 6.0
string msg2 = $"用户 {name},年龄 {age}";

// 支持表达式
string msg3 = $"用户 {name.ToUpper()},年龄 {age},成年人:{age >= 18}";

// 格式化
decimal price = 1234.5m;
string msg4 = $"价格:{price:C2}"; // ¥1,234.50
string msg5 = $"日期:{DateTime.Now:yyyy-MM-dd}";

// 多行(结合 @"")
string sql = $@"
SELECT * FROM Users
WHERE Name = '{name}'
AND Age > {age}
";

注意:字符串插值编译后会转换为 string.Format,性能与之相当。如果在循环中拼接大量字符串,仍应使用 StringBuilder

3. 表达式体成员(Expression-bodied Members)

单行方法和属性不再需要大括号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 旧写法
public string FullName
{
get { return $"{FirstName} {LastName}"; }
}

public string GetGreeting(string name)
{
return $"Hello, {name}!";
}

// C# 6.0
public string FullName => $"{FirstName} {LastName}";
public string GetGreeting(string name) => $"Hello, {name}!";

// 也适用于运算符重载
public static Vector operator +(Vector a, Vector b)
=> new Vector(a.X + b.X, a.Y + b.Y);

对于 DTO 和简单的领域对象,这个特性让代码行数减少了约 30%,可读性也提升了。

4. nameof 运算符

告别硬编码的字符串属性名:

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
// 旧写法(脆弱:重命名属性后这里不会自动更新)
public string Name
{
set
{
_name = value;
OnPropertyChanged("Name");
}
}

// C# 6.0(重构安全)
public string Name
{
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}

// 参数校验
public void SetAge(int age)
{
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age), "年龄不能为负数");
}

// 日志
Log.Info($"方法 {nameof(GetUsers)} 开始执行");

nameof 在编译期求值,没有运行时开销,也不会被混淆工具处理掉(字符串字面量会被混淆)。

5. 字典初始化语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 旧写法
var config = new Dictionary<string, string>
{
{ "host", "localhost" },
{ "port", "5432" },
{ "db", "myapp" }
};

// C# 6.0
var config = new Dictionary<string, string>
{
["host"] = "localhost",
["port"] = "5432",
["db"] = "myapp"
};

6. 异常过滤器 when

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
// 旧写法:catch 所有,再在内部判断,如果不符合条件还得 rethrow
try
{
// ...
}
catch (HttpRequestException ex)
{
if (ex.Message.Contains("404"))
{
HandleNotFound();
}
else
{
throw; // rethrow 会丢失原始栈信息!
}
}

// C# 6.0:过滤器在栈展开之前执行,保留完整栈信息
try
{
// ...
}
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
{
HandleNotFound();
}
catch (HttpRequestException ex) when (ex.Message.Contains("500"))
{
HandleServerError();
}

when 过滤器还有一个强大用途:调试时记录所有异常而不干扰异常传播

1
2
3
4
5
catch (Exception ex) when (Log.Error("异常发生", ex) == false)
{
// Log.Error 返回 false,when 条件不满足,异常继续传播
// 但异常信息已经被记录下来了
}

7. using static

1
2
3
4
5
6
using static System.Math;
using static System.Console;

// 之后可以直接调用静态成员,无需类名
double result = Sqrt(Pow(3, 2) + Pow(4, 2)); // 5
WriteLine($"结果:{result}");

对于频繁使用 MathConsoleEnumerable 的代码,using static 可以减少很多重复的类名前缀。

总结

C# 6.0 的新特性按实用价值排序:

特性 实用价值 使用频率
?. null 条件运算符 ★★★★★ 极高
$"" 字符串插值 ★★★★★ 极高
nameof ★★★★☆
表达式体成员 ★★★★☆
异常过滤器 when ★★★☆☆
using static ★★★☆☆
字典初始化 ★★☆☆☆

?.$"" 是立竿见影的,升级到 C# 6.0 第一天就应该开始用。nameof 在重构频繁的项目里非常有价值,能让 IDE 的重命名功能真正安全地工作。

作者 · authorzt
发布 · date2016-03-25
篇幅 · length1.3k 字 · 3 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论