从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览
作者 | 雪轻鸿 责编 | 张红月
来源 | https://qinyuanpei.blog.csdn.net/article/details/113720157
C# 版本历史记录![](/assets/blank.gif)
说明:因为Markdown下维护这样复杂的表格有一点麻烦,故,这里以图片形式展示出来,如后续内容有更新,请访问原始笔记链接。为知笔记的表格渲染在移动端表现不佳,为了获得更好的阅读体验,请在电脑端访问查看。
C# 版本特性说明
现在是 2021 年,相信 C# 7.0 以前的版本大家都应该没有什么问题,因为像博主这样的 90 后“中年”男人,接触的都是这个版本的 C#。所以,在这里我们主要讲解大家C# 7.0、8.0 以及 9.0 的语法特性。考虑到文章篇幅有限,这里选取的都是博主个人比较喜欢的语法特性,如果这里没有你喜欢的特性,请参考文章末尾的参考链接。如果这里的特性你都不喜欢,请你马上关掉这个网页,愿这个世界:Love & Peace。可能你会感觉到我说话变得小心翼翼起来,因为这个世界上有种叫做“杠精”的生物,当它从我的只言片语里读出那些挫败感的时候,终于有了嘲笑我们这批步入30岁行列的90后的底气,没错,我在最近的博客评论中被读者“嘲讽”了,让暴风雨来得更猛烈一些吧!
C# 7.0
在 C# 7.0 中,我个人比较喜欢的特性主要有以下几个:元组和弃元、更多的 expression-bodied 成员、out 变量、异步 Main 方法、模式匹配 和 引发表达式。
元组和弃元
这个概念乍听起来可能会有一点陌生,其实,按我的理解,这就是增强的元组语法,终于可以摆脱Item1、Item2…啦:
//示例1(string Alpha, string Beta) namedLetters = ("a", "b");Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}"); //示例2var alphabetStart = (Alpha: "a", Beta: "b");Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");//示例3int count = 5;string label = "Colors used in the map";var pair = (count, label);Console.WriteLine(pair);
有一段时间,前端同事总和我吹嘘 ES6 里面的解构多么多么好用!对此,我想说,C# 一样可以解构,假设我们现在有下面的一个方法:
static (string, double, double) GetLocation() { var city = "西安市"; var lat = 33.42d; var lon = 107.40d; return (city, lon, lat);}
这就是简化后的元组的用法,如果是以前,我们还需要返回一个Tuple<string, double, double>。此时,如果我们需要解析城市名称及其经纬度,可以这样做:
//示例4(string city, double lon, double lat) = GetLocation();Console.WriteLine($"{city},({lon},{lat})");
OK,那么什么又是弃元呢?继续以上面的代码为例,如果我不关心经纬度,只需要城市名称又该怎么办呢?人家的方法返回的是一个3元的结果,而我们只需要其中的1元,此时,就有了所谓弃元的概念:
(string city, _, _) = GetLocation();Console.WriteLine($"{city}");
在 C# 中可以使用下划线_来表示要舍弃的元,是为弃元,怎么样?你学会了吗?
更多的 expression-bodied 成员
这部分同样是经过强化的 Lambda 表达式,之前我们可以在成员函数和 只读属性上使用 Lambda 表达式,而现在,我们可以将其运用在构造函数、终结器以及 get和set访问器:
// Expression-bodied constructorpublic ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
private string label;
// Expression-bodied get / set accessors.public string Label{ get => label; set => this.label = value ?? "Default label";}
out变量
个人认为,这是一个非常不错的改进,终于不用再单独声明out变量啦:
if (int.TryParse(input, out int result)) Console.WriteLine(result);else Console.WriteLine("Could not parse input");
异步 Main 方法
顾名思义,Main 方法现在可以支持 async 关键字啦:
static async Task<int> Main(){ // This could also be replaced with the body // DoAsyncWork, including its await expressions: return await DoAsyncWork();}
在没有返回值的情况下,可以考虑返回Task:
static async Task Main(){ await SomeAsyncMethod();}
模式匹配
主要是针对 is 和 switch 语句提供了增强的语法。在这里,对于前者来说,我们可以将判断和赋值两个步骤合二为一:
public static double ComputeAreaModernIs(object shape){ if (shape is Square s) return s.Side * s.Side; else if (shape is Circle c) return c.Radius * c.Radius * Math.PI; else if (shape is Rectangle r) return r.Height * r.Length; // elided throw new ArgumentException( message: "shape is not a recognized shape", paramName: nameof(shape));}
而对于后者来说,主要打破了传统 switch 语句的常量模式:
public static double ComputeArea_Version3(object shape){ switch (shape) { case Square s when s.Side == 0: case Circle c when c.Radius == 0: return 0;case Square s: return s.Side * s.Side; case Circle c: return c.Radius * c.Radius * Math.PI; default: throw new ArgumentException( message: "shape is not a recognized shape", paramName: nameof(shape)); }}
引发表达式
这个主要是针对 throw 关键字的增强,当我看到微软的文档的时候,我突然意识到,这个语法其实我用了很久啦!
//场景A:条件运算符string arg = args.Length >= 1 ? args[0] : throw new ArgumentException("You must supply an argument");//场景B:Null合并运算符public string Name{ get => name; set => name = value ?? throw new ArgumentNullException( paramName: nameof(value), message: "Name cannot be null");}//场景C:Lambda表达式DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
以上,就是 C# 7.0 中我个人比较喜欢的语法特性。需要了解所有 C# 7.0 语法特性的小伙伴们,则可以参考这里:C# 7.0 - C# 7.3 中的新增功能。
C# 8.0
在 C# 8.0 中,我个人比较喜欢的特性主要有以下几个:默认接口方法、异步流、索引和范围。
默认接口方法
关于这个,我觉得有点多此一举,如果一定要有一个默认行为,那你用继承来实现不就好啦,接口本来就是用来实现的啊摔!
public class ChineseSayHello : ISayHello{ public string Who { get; set; }}
public interface ISayHello{ private const string DefaultPersopn = "Anumouse"; string Who { get; set; } void SayHello() { Who = DefaultPersopn; Console.WriteLine($"Hello, {Who}"); } }
在上面这个例子里,ChineseSayHello没有实现SayHello()方法不影响编译,因为ISayHello有默认实现,可正因为如此,SayHello()方法属于ISayHello,不属于ChineseSayHello:
//正确,可以编译var sayHello = new ChineseSayHello() as ISayHello;sayHello.SayHello();//错误,无法编译 var sayHello = new ChineseSayHello();sayHello.SayHello();
异步流
该特性可以看作是IEnumerable<T>的一个延伸,即IAsyncEnumerable<T>,主要有下面三个属性:
它是用 async 修饰符声明的。
它将返回 IAsyncEnumerable。
该方法包含用于在异步流中返回连续元素的 yield return 语句。
下面是一个来自微软官方的基本示例:
//生成异步流public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence(){ for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; }}//枚举异步流await foreach (var number in GenerateSequence()){ Console.WriteLine(number);}
和异步流相关的一个概念是:异步可释放,即 System.IAsyncDisposable,这个可以参考:实现 DisposeAsync 方法。
索引和范围
关于这个,我们换一种说法,可能大家就能接受啦!是什么呢?答案是:切片。切片语法博主经常在 Python 中使用,想不到有生之年居然可以在 C# 里用到这个语法。不过,这个语法糖怎么看都不甜啊,因为没那味儿!
var words = new string[]{ // index from start index from end "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1}; //取最后一个元素Console.WriteLine($"The last word is {words[^1]}");//获取第一个元素到第三个元素var quickBrownFox = words[1..4];//获取倒数第一个元素到倒数第二个元素var lazyDog = words[^2..^0];//获取全部元素var all = words[..];//获取开始到第三个元素var firstPhrase = words[..4];//获取结束到倒数第二个元素var lastPhrase = words[6..];
看起来这些东西在 Python 里都有啊,到底是哪里除了问题呢?我觉得更多的是符号上的不同吧, ^ 这个符号除了表示指数的意思以外,还有按位进行异或运算的意思,所以,这个语法糖加进来以后就会显得相当混乱,而 .. 这个符号显然没有 : 写起来方便啊,所以,虽然 C# 从 C# 8.0 开始有了切片语法,可这不是我想要的切片语法啊!
以上,就是 C# 8.0 中我个人比较喜欢的语法特性。需要了解所有 C# 8.0 语法特性的小伙伴们,则可以参考这里:C# 8.0 中的新增功能。
C# 9.0
在 C# 9.0 中,我个人比较喜欢的特性主要有以下几个:Record、顶级语句、模式匹配增强。
Record
record 是 C# 9.0 中提供的一个新的关键字,地位上等同于 class 和 struct,中文翻译为:记录类型。这是一种引用类型,它提供合成方法来提供值语义,从而实现相等性。默认情况下,记录是不可变的。简而言之,record 是不可变的引用类型。你可能会说,我们为什么要搞这么一个类型出来呢?难道 class 不香吗?
我觉得如果要回答这个问题,可以借鉴 DDD 中的实体 和 值对象这两个概念。实体 通常都有一个唯一的标识并且在整个生命周期中具有连续性,这一类角色通过 class 来实现一直都工作得很好。例如,每一个 User 都会有一个唯一的UserId ,我们使用 UserId 来判断其相等性。而 值对象 则是指那些没有唯一的标识、不可变的、通过属性来判断相等性。例如,我们有一个地址 Address,它由省、市、区、县和详细地址组成,那么,问题来了,如果两个 Address 的省、市、区、县和详细地址都相同,这两个 Address 是不是同一个地址呢?常识告诉我们:不会,因为它们是不同的实例。
这就是 record 出现的原因,对于上面的这个问题,我们可以来解决:
record Address{ public string Province { get; set; } public string City { get; set; } public string District { get; set; } public string County { get; set; }}
var addr1 = new Address() { Province = "陕西省", City = "西安市", District = "雁塔区" };var addr2 = new Address() { Province = "陕西省", City = "西安市", District = "雁塔区" };Console.WriteLine($"addr1 == addr2:{addr1 == addr2}");
想想以前我们是怎么做的呢?是不是要写类似下面这样的代码:
if (addr1.Province == addr2.Province && addr1.City == addr2.City) {//属性太多啦,我就不一个一个地比较啦,懂得都懂}
所以,这就是 record 存在的意义。除此之外呢,这个关键字更多的是语法层面上的,实际上从编译出来的 IL 来看,它本质上依然是一个类,并且它是不可变的。定义记录类型时,编译器会合成其他几种方法:
基于值的相等性比较方法
替代 GetHashCode()
复制和克隆成员
PrintMembers 和 ToString()
那么,你可能还会有疑问,假如我定义了两个不同的记录类型,它们都拥有相同的属性成员,如果按值相等来判断的话,岂不是这两个不同的记录类型变成相同的了?这么重要的问题,微软怎么可能没有想到呢?编译器会合成一个 EqualityContract 属性,该属性返回与记录类型匹配的 Type 对象。在这里,微软再一次发挥了元组的威力,对于上面定义的地址,我们可以继续使用解构语法:
(province, city, district, county) = addr1;
当然,我相信哪怕到2090年,这个世界上依然会有“杠精”:你说这玩意儿不能变?我就想变怎么办?答案是使用with语法:
public record Person{ public string LastName { get; } public string FirstName { get; }public Person(string first, string last) => (FirstName, LastName) = (first, last);}
var person = new Person("Bill", "Wagner");Person brother = person with { FirstName = "Paul" }; // 修改FirstName的副本Person clone = person with { }; // 空集副本
好了,关于记录类型就先为大家介绍到这里,更详细的说明可以参考这里:使用记录类型。
顶级语句
顶级语句,这个又是一个听起来非常模糊的概念对不对?大家可以看一下这篇文章:26 种不同的编程语言的 “Hello World” 程序。怎么样,在众多解释型的语言中,C#、Java 甚至 C++ 的 “Hello World” 是不是都看起来有一点臃肿?
好了,现在可以梦想成真啦!
using System;
Console.WriteLine("Hello World!");
如果觉得这样还显得臃肿,可以省略 using 部分:
System.Console.WriteLine("Hello World!");
当然啦,一个项目里显然只能有一个文件可以使用顶级语句,你可以理解为这些代码运行在一个看不见的Main()方法中,而Main()方法显然只能有一个,相比下来,Python 就自由多啦,不过if __name__ == '__main__'的老梗就不再这里展开啦!
模式匹配增强
感觉微软在模式匹配的道路上越走越远啊,说好的语法糖呢?这简直是毒药,7.0 里面眼花缭乱的switch都还没学会呢!
public static bool IsLetter(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
public static bool IsLetterOrSeparator(this char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
if (e is not null){ // ...}
以上,就是 C# 9.0 中我个人比较喜欢的语法特性。需要了解所有 C# 9.0 语法特性的小伙伴们,则可以参考这里:C# 9.0 中的新增功能。
以上。
☞抽成 30% 的苹果税是良心价?谷歌也学“坏”了☞众善之源 or 万恶之源?详析微服务的好与坏☞“面向对象就是一个错误!”☞回溯 Rust 2020:正在成为最受欢迎的编程语言
从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览相关推荐
- Djang1.8+Python2.0迁移到Django2.0+Python3.6注意事项(转)
Djang1.8+Python2.0迁移到Django2.0+Python3.6注意事项 参考:https://blog.csdn.net/weixin_40475396/article/detail ...
- mysql 8.0 docker_Docker安装MySQL8.0的实现方法
环境:MacOS_Cetalina_10.15.1.Mysql8.0.18.Docker_2.0.0.3 1.docker仓库搜索mysql docker search mysql 2.docker仓 ...
- RedHat 7.0及CentOS 7.0禁止Ping的三种方法
作者:荒原之梦 原文链接:http://zhaokaifeng.com/?p=538 前言: "Ping"属于ICMP协议(即"Internet控制报文协议") ...
- 【Linux】在VirtualBox-6.0中安装Manjaro18.0
1.参考博客: VMware虚拟机下Manjaro17.1.6安装详细教程 2.在VirtualBox-6.0中安装Manjaro18.0 1)基本步骤和博客中安装17.1.6相同,下面只记录不同的. ...
- Thorntail 2.2.0提供从WildFly Swarm自动迁移的特性
自6月底宣布把WildFly Swarm2018.5.0改名为Thorntail2.0.0以来,Red Hat在8月中旬以后的三个周里发布了Thorntail 2.1.0版本和2.2.0版本.除了许多 ...
- 编译可在Nexus5上运行的CyanogenMod13.0 ROM(基于Android6.0)
编译可在Nexus5上运行的CyanogenMod13.0 ROM (基于Android6.0) 作者:寻禹@阿里聚安全 前言 下文中无特殊说明时CM代表CyanogenMod的缩写. 下文中说的&q ...
- 从零开始学习tensorflow2.0之熟悉tf2.0的数据
导入tensorflow2.0 安装tensorflow2.0,使用pip安装,在jupyter notebook之中 !pip install tensorflow !pip install ten ...
- 记selenium1.0升级到selenium2.0
前阵子因为要进行支持多浏览器的自动化测试,原来selenium1.0仅支持到firefox3.6,IE8,chrome4的版本,而公司GA数据显示用户多使用IE9,Firefox20,chrome26 ...
- 更改mvc版本的时候,手动修改交3.0改到4.0,将razor改到2.0,仍然提示出现错误,mvc3.0...
偶然碰到,更改mvc版本,由3.0升级到4.0,但是依然提示3.0的错误 几经周折排查,最终发现 <runtime><assemblyBinding xmlns="urn: ...
最新文章
- python web为什么不火-编程语言里的明星:Python为什么突然不火了?
- 数据结构 二叉树的遍历
- 数据分析究竟该如何学?大神总结的6种数据分析思维到底如何?
- 安卓Day16-bug
- Linux -- 进程或线程独占CPU
- python 画线置顶_绘制分支[置顶] python学习~递归函数
- CentOS下openssh版本降级
- POJ 2752 Seek the Name, Seek the Fame
- strip符号和节移除工具,符号表删除
- asp.net电费管理系统
- 天池- IJCAI-18 阿里妈妈搜索广告转化预测(完整版代码,数据集等总结)
- Log4j2漏洞发展历程及解决方案
- Java 10年回顾:世界各地开发高手谈Java
- 乙女心,养的第一株多肉小植物
- IT桔子在线教育沙龙
- Arduino与Proteus仿真实例-电子相册仿真
- 我给了老公3个月的花心期!爱还会再回来吗?
- Flutter 状态管理之Bloc下
- 均匀分布 卡方分布_深度学习需要掌握的13个概率分布(附代码)
- 又找了几篇pix的 相关配置,看看吧