C# 8.0 新特性:可空引用类型与模式匹配
2019.04.08
C#
4 min
1.4k 字
// 目录 · contents
1. 可空引用类型(Nullable
Reference Types) 2. 增强的模式匹配 Switch 表达式(替代 switch
语句) 属性模式 元组模式 位置模式(解构匹配) 3. 异步流(Async Streams) 4. using 声明(简化资源释放) 5. 接口默认实现(谨慎使用) 总结
C# 8.0 和 .NET Core 3.0
同步发布(预览版已经可以用),这个版本最令人期待的是可空引用类型 (Nullable
Reference Types)——这是 C# 自诞生以来对 null
问题最彻底的一次正面回应。
1. 可空引用类型(Nullable
Reference Types)
C# 历来的痛点:引用类型默认可以是
null,但编译器没有任何警告,运行时才抛
NullReferenceException。
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 string name = null ; string ? nullableName = null ; void PrintName (string ? name ) { Console.WriteLine(name.Length); if (name != null ) { Console.WriteLine(name.Length); } Console.WriteLine(name?.Length ?? 0 ); Console.WriteLine(name!.Length); }
在现有项目中逐步启用:
1 2 3 4 5 6 7 8 #nullable enable public class UserService { public User? FindById(int id) { ... } public User GetOrThrow (int id ) { ... } }#nullable restore
这个特性是静态分析 ,不影响运行时行为,只是让编译器帮你提前发现潜在的
NullReferenceException。
2. 增强的模式匹配
C# 7.0 引入了基础模式匹配,8.0 大幅增强:
Switch 表达式(替代 switch
语句)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 string GetDiscount (UserLevel level ) { switch (level) { case UserLevel.Bronze: return "5%" ; case UserLevel.Silver: return "10%" ; case UserLevel.Gold: return "15%" ; default : return "0%" ; } }string GetDiscount (UserLevel level ) => level switch { UserLevel.Bronze => "5%" , UserLevel.Silver => "10%" , UserLevel.Gold => "15%" , _ => "0%" };
属性模式
1 2 3 4 5 6 7 8 9 10 decimal CalculateShipping (Order order ) => order switch { { Weight: < 1 , IsExpress: false } => 5.00 m, { Weight: < 5 , IsExpress: false } => 10.00 m, { Weight: >= 5 , IsExpress: false } => 15.00 m, { IsExpress: true , Weight: < 1 } => 15.00 m, { IsExpress: true } => 25.00 m, _ => throw new ArgumentException("Invalid order" ) };
元组模式
1 2 3 4 5 6 7 8 string GetTrafficLight (int hour, bool isWeekend ) => (hour, isWeekend) switch { (>= 7 and <= 9 , false ) => "红灯(早高峰)" , (>= 17 and <= 19 , false ) => "红灯(晚高峰)" , (_, true ) => "绿灯(周末)" , _ => "正常通行" };
位置模式(解构匹配)
1 2 3 4 5 6 7 8 9 10 public record Point (int X, int Y ) ;string ClassifyPoint (Point p ) => p switch { (0 , 0 ) => "原点" , (var x, 0 ) when x > 0 => "正 X 轴" , (0 , var y) when y > 0 => "正 Y 轴" , (var x, var y) when x > 0 && y > 0 => "第一象限" , _ => "其他" };
3. 异步流(Async Streams)
这是 .NET Core 3.0 的重大特性,终于支持
async IAsyncEnumerable<T>:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public async IAsyncEnumerable<StockPrice> GetPriceUpdatesAsync ( string symbol, [EnumeratorCancellation] CancellationToken cancellationToken = default ) { while (!cancellationToken.IsCancellationRequested) { var price = await _api.GetPriceAsync(symbol); yield return price; await Task.Delay(TimeSpan.FromSeconds(1 ), cancellationToken); } }await foreach (var price in GetPriceUpdatesAsync ("AAPL" , cts.Token )) { Console.WriteLine($"AAPL: {price.Value} " ); }
之前要实现类似功能需要用 IObservable<T> +
Rx.NET,现在语言层面直接支持了。
4. using 声明(简化资源释放)
1 2 3 4 5 6 7 8 9 10 11 using (var reader = new StreamReader(path)) { var content = reader.ReadToEnd(); } using var reader = new StreamReader(path);var content = reader.ReadToEnd();
5. 接口默认实现(谨慎使用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface ILogger { void Log (string message ) ; void LogWarning (string message ) { Log($"[WARNING] {message} " ); } void LogError (string message ) { Log($"[ERROR] {message} " ); } }public class ConsoleLogger : ILogger { public void Log (string message ) => Console.WriteLine(message); }
个人建议 :谨慎使用接口默认实现。它主要用于解决接口演化问题(给现有接口加方法而不破坏实现类),不应该作为常规设计手段——那是抽象类的职责。
总结
C# 8.0 最值得立即使用的特性:
可空引用类型
消灭 NullReferenceException
需要逐步修复警告
Switch 表达式
简洁的条件分支
低,可逐步替换
属性/元组模式
复杂条件更可读
低
异步流
真正的异步迭代
需要 .NET Core 3.0+
using 声明
减少嵌套
极低
可空引用类型是这个版本最重要的特性,虽然开启后需要处理一些警告,但长远来看能显著减少线上
NullReferenceException
的数量。建议新项目直接开启,老项目按模块逐步启用。
在这之后不久,我开始把更多精力转向 Java
生态(主要原因是公司主要技术栈迁移),但 C#
这些年积累的开发习惯——特别是强类型、LINQ、async/await——对后来学习 Java
的很多概念很有帮助。