C# 7.0 的新特性(速览版)
《〔译〕 C# 7 的新特性》花了很大的篇幅来介绍 C# 7.0 的 9 个新特性,这里我根据项目经验,通过实例对它们进行一个快速的介绍,让大家能在短时间内了解它们。
总的来说,这些新特性使 C# 7.0 更容易以函数式编程的思想来写代码,C# 6.0 在这条路上已经做了不少工作, C# 7.0 更近一步!
表达式 everywhere
C# 6.0 中,可以对成员方法和只读属性使用 Lambda 表达式,当时最郁闷的就是为什么不支持属性的 set 访问器。现在好了,不仅 set 方法器支持使用 Lambda 表达式,构造方法、析构方法以及索引都支持以 Lambda 表达式方式定义了。
class SomeModel { private string internalValue; public string Value { get => internalValue; set => internalValue = string.IsNullOrWhiteSpace(value) ? null : value; } }
out
变量
out
变量是之前就存在的语法,C# 7.0 只是允许它将申明和使用放在一起,避免多一行代码。最直接的效果,就是可以将两个语句用一个表达式完成。这里以一个简化版的 Key
类为例,这个类早期被我们用于处理通过 HTTP Get/Post 传入的 ID 值。
public class Key { public string Value { get; } public Key(string key) { Value = key; } public int IntValue { get { // C# 6.0,需要提前定义 intValue,但不需要初始化 // 虽然 C# 6.0 可以为只读属性使用 Lambda 表达式 // 但这里无法用一个表达式表达出来 int intValue; return int.TryParse(Value, out intValue) ? intValue : 0; } } }
而在 C# 7 中就简单了
// 注意 out var intValue, // 对于可推导的类型甚至可以用 var 来申明变量 public int IntValue => int.TryParse(Value, out var intValue) ? intValue : 0;
元组和解构
用过 System.Tuple
的朋友一定对其 Item1
、Item2
这样毫无语义的命名深感不爽。不过 C# 7.0 带来了语义化的命名,同时,还减化了元组的创建,不再需要 Tuple.Create(...)
。另外,要使用新的元组特性和解构,需要引入 NuGet 包 System.ValueTuple
。
Install-Package System.ValueTuple
当然,元组常用于返回多个值的方法。也有些人喜欢用 out
参数来返回,但即使现在可以 out
变量,我仍然不赞成广泛使用 out
参数。
下面这个示例方法用于返回一个默认的时间范围(从今天开始算往前一共 7 天),用于数据检索。
// 返回类型是一个包含两个元素的元组 (DateTime Begin, DateTime End) GetDefaultDateRange() { var end = DateTime.Today.AddDays(1); var begin = end.AddDays(-7); // 这里使用一对圆括号就创建了一个元组 return (begin, end); }
调用这个方法可以获得元组,因为定义的时候返回值指定了每个数据成员的名称,所以从元组获取数据可以是语义化的,当然仍然可以使用 Item1
和 Item2
。
var range = GetDefaultDateRange(); var begin = range.Begin; // 也可以 begin = range.Item1 var end = range.End; // 也可以 end = range.Item2
上面这个例子还可以简化,不用 range
这个中间变量,这就用到了解构
var (begin, end) = GetDefaultDateRange();
这里创建元组是以返回值来举例的,其实它就是一个表达式,可以在任何地方创建元组。上面的例子逻辑很简单,可以用表达式解决。下面的示例顺便演示了非语义化的返回类型申明。
// 原来的 (DateTime Begin, DateTime End) 申明也是没问题的 (DateTime, DateTime) GetDefaultDateRange() => (DateTime.Today.AddDays(1).AddDays(-7), DateTime.Today.AddDays(1));
解构方法 Deconstrct
解构方法可以让任何类(而不仅仅是元组)按定义的参数进行解构。而且神奇的是解构方法可以是成员方法,也可以定义成扩展方法。
public class Size { public int Width { get; } public int Height { get; } public int Tall { get; } public Size(int width, int height, int tall) { this.Width = width; this.Height = height; this.Tall = tall; } // 定义成成员方法的解构 public void Deconstruct(out int width, out int height) { width = Width; height = Height; } } public static class SizeExt { // 定义成扩展方法的解构 public static void Deconstruct(this Size size, out int width, out int height, out int tall) { width = size.Width; height = size.Height; tall = size.Tall; } }
下面是使用解构的代码
var size = new Size(1920, 1080, 10); var (w, h) = size; var (x, y, z) = size;
改造 Size
的构造方法
还记得前面提到的构造方法可以定义为 Lambda 表达式吗?下面是使用元组和 Lambda 对 Size 构造方法的改造——我已经醉了!
public Size(int width, int height, int tall) => (Width, Height, Tall) = (width, height, tall);
模式匹配
模式匹配目前支持 is
和 switch
。说起来挺高大上的一个名字,换个接地气一点的说法就是判断类型顺便定义个具体类型的引用,有兴趣还可以加再点额外的判断。
对于 is
来说,就是判断的时候顺便定义个变量再初始化一下,所以像原来这样写的代码
// 假设逻辑能保证这里的 v 可能是 string 也 可能是 int string ToString(object v) { if (v is int) { int n = (int) v; return n.ToString("X4"); } else { return (string) n; } }
可以简化成——好吧,直接一步到位写成表达式好了
string ToString(object v) => (v is int n) ? n.ToString("X4") : (string) v;
当然你可能说之前的那个也可以简化成一个表达式——好吧,不深究这个问题好吗?我只是演示
is
的模式匹配而已。
而 switch
中的模式匹配似乎要有用得多,还是以 ToString 为例吧
static string ToString(object v) { switch (v) { case int n when n > 0xffff: // 判断类型,匹配的情况下再对值进行一个判断 return n.ToString("X8"); case int n: // 判断类型,这里 n 肯定 <= 0xffff return n.ToString("X4"); case bool b: return b ? "ON" : "OFF"; case null: return null; default: return v.ToString(); } }
注意一下上面第一个分支中 when
的用法就好了。
ref 局部变量和 ref 返回值
这已经是很接近 C/C++ 的一种用法了。虽然官方说法是这样做可以解决一些安全性问题,但我个人目前还是没遇到它的使用场景。如果设计足够好,在目前又加入了元组新特性和解构的情况下,个人认为几乎可以避免使用 out
和 ref
。
既然没用到,我也不多说了,有用到的同学来讨论一下!
数字字面量语法增强
这里有两点增强,一点是引入了 0b
前缀的二进制数字面量语法,另一点是可以在数值字面量中任意使用 _
对数字进行分组。这个不用多数,举两个例就明白了
const int MARK_THREE = 0b11; // 0x03 const int LONG_MARK = 0b_1111_1111; // 0xff const double PI = 3.14_1592_6536
局部函数
经常写 JavaScript 的同学肯定会深有体会,局部函数是个好东西。当然它在 C# 中带来的最大好处是将某些代码组织在了一起。我之前在项目中大量使用了 Lambda 来代替局部函数,现在可以直接替换成局部函数了。Labmda 和局部函数虽然多数情况下能做同样的事情,但是它们仍然有一些区别
对于 Lambda,编译器要干的事情比较多。总之呢,就是编译效率要低得多
Lambda 通过委托实现,调用过程比较复杂,局部函数可以直接调用。简单地说就是局部函数执行效率更高
Lambda 必须先定义再使用,局部函数可以定义在使用之后。据说这在对递归算法的支持上会有区别
比较常用的地方是 Enumerator 函数和 async 函数中,因为它们实际都不是立即执行的。
我在项目中多是用来组织代码。局部函数代替只被某一个公共 API 调用的私有函数来组织代码虽然不失为一个简化类结构的好方法,但是把公共 API 函数的函数体拉长。所以很多时候我也会使用内部类来代替某些私有函数来组织代码。这里顺便说一句,我不赞成使用 #region
组织代码。
支持更多 async 返回类型
如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比较像 <code>Promise
的角色。不用羡慕 JavaScript 的 async 支持 Promise like,现在 C# 的 async 也支持 Task like 了,只要实现了 GetAwaiter
方法就行。
官方提供了一个 ValueTask
作为示例,可以通过 NuGet 引入:
Install-Package System.Threading.Tasks.Extensions
这个 ValueTask
比较有用的一点就是兼容了数据类型和 Task:
string cache; ValueTask<string> GetData() { return cache == null ? new ValueTask<string>(cache) : new ValueTask<string>(GetRemoteData()); // 局部函数 async Task<string> GetRemoteData() { await Task.Delay(100); return "hello async"; } }
《〔译〕 C# 7 的新特性》花了很大的篇幅来介绍 C# 7.0 的 9 个新特性,这里我根据项目经验,通过实例对它们进行一个快速的介绍,让大家能在短时间内了解它们。
总的来说,这些新特性使 C# 7.0 更容易以函数式编程的思想来写代码,C# 6.0 在这条路上已经做了不少工作, C# 7.0 更近一步!
表达式 everywhere
C# 6.0 中,可以对成员方法和只读属性使用 Lambda 表达式,当时最郁闷的就是为什么不支持属性的 set 访问器。现在好了,不仅 set 方法器支持使用 Lambda 表达式,构造方法、析构方法以及索引都支持以 Lambda 表达式方式定义了。
class SomeModel { private string internalValue; public string Value { get => internalValue; set => internalValue = string.IsNullOrWhiteSpace(value) ? null : value; } }
out
变量
out
变量是之前就存在的语法,C# 7.0 只是允许它将申明和使用放在一起,避免多一行代码。最直接的效果,就是可以将两个语句用一个表达式完成。这里以一个简化版的 Key
类为例,这个类早期被我们用于处理通过 HTTP Get/Post 传入的 ID 值。
public class Key { public string Value { get; } public Key(string key) { Value = key; } public int IntValue { get { // C# 6.0,需要提前定义 intValue,但不需要初始化 // 虽然 C# 6.0 可以为只读属性使用 Lambda 表达式 // 但这里无法用一个表达式表达出来 int intValue; return int.TryParse(Value, out intValue) ? intValue : 0; } } }
而在 C# 7 中就简单了
// 注意 out var intValue, // 对于可推导的类型甚至可以用 var 来申明变量 public int IntValue => int.TryParse(Value, out var intValue) ? intValue : 0;
元组和解构
用过 System.Tuple
的朋友一定对其 Item1
、Item2
这样毫无语义的命名深感不爽。不过 C# 7.0 带来了语义化的命名,同时,还减化了元组的创建,不再需要 Tuple.Create(...)
。另外,要使用新的元组特性和解构,需要引入 NuGet 包 System.ValueTuple
。
Install-Package System.ValueTuple
当然,元组常用于返回多个值的方法。也有些人喜欢用 out
参数来返回,但即使现在可以 out
变量,我仍然不赞成广泛使用 out
参数。
下面这个示例方法用于返回一个默认的时间范围(从今天开始算往前一共 7 天),用于数据检索。
// 返回类型是一个包含两个元素的元组 (DateTime Begin, DateTime End) GetDefaultDateRange() { var end = DateTime.Today.AddDays(1); var begin = end.AddDays(-7); // 这里使用一对圆括号就创建了一个元组 return (begin, end); }
调用这个方法可以获得元组,因为定义的时候返回值指定了每个数据成员的名称,所以从元组获取数据可以是语义化的,当然仍然可以使用 Item1
和 Item2
。
var range = GetDefaultDateRange(); var begin = range.Begin; // 也可以 begin = range.Item1 var end = range.End; // 也可以 end = range.Item2
上面这个例子还可以简化,不用 range
这个中间变量,这就用到了解构
var (begin, end) = GetDefaultDateRange();
这里创建元组是以返回值来举例的,其实它就是一个表达式,可以在任何地方创建元组。上面的例子逻辑很简单,可以用表达式解决。下面的示例顺便演示了非语义化的返回类型申明。
// 原来的 (DateTime Begin, DateTime End) 申明也是没问题的 (DateTime, DateTime) GetDefaultDateRange() => (DateTime.Today.AddDays(1).AddDays(-7), DateTime.Today.AddDays(1));
解构方法 Deconstrct
解构方法可以让任何类(而不仅仅是元组)按定义的参数进行解构。而且神奇的是解构方法可以是成员方法,也可以定义成扩展方法。
public class Size { public int Width { get; } public int Height { get; } public int Tall { get; } public Size(int width, int height, int tall) { this.Width = width; this.Height = height; this.Tall = tall; } // 定义成成员方法的解构 public void Deconstruct(out int width, out int height) { width = Width; height = Height; } } public static class SizeExt { // 定义成扩展方法的解构 public static void Deconstruct(this Size size, out int width, out int height, out int tall) { width = size.Width; height = size.Height; tall = size.Tall; } }
下面是使用解构的代码
var size = new Size(1920, 1080, 10); var (w, h) = size; var (x, y, z) = size;
改造 Size
的构造方法
还记得前面提到的构造方法可以定义为 Lambda 表达式吗?下面是使用元组和 Lambda 对 Size 构造方法的改造——我已经醉了!
public Size(int width, int height, int tall) => (Width, Height, Tall) = (width, height, tall);
模式匹配
模式匹配目前支持 is
和 switch
。说起来挺高大上的一个名字,换个接地气一点的说法就是判断类型顺便定义个具体类型的引用,有兴趣还可以加再点额外的判断。
对于 is
来说,就是判断的时候顺便定义个变量再初始化一下,所以像原来这样写的代码
// 假设逻辑能保证这里的 v 可能是 string 也 可能是 int string ToString(object v) { if (v is int) { int n = (int) v; return n.ToString("X4"); } else { return (string) n; } }
可以简化成——好吧,直接一步到位写成表达式好了
string ToString(object v) => (v is int n) ? n.ToString("X4") : (string) v;
当然你可能说之前的那个也可以简化成一个表达式——好吧,不深究这个问题好吗?我只是演示
is
的模式匹配而已。
而 switch
中的模式匹配似乎要有用得多,还是以 ToString 为例吧
static string ToString(object v) { switch (v) { case int n when n > 0xffff: // 判断类型,匹配的情况下再对值进行一个判断 return n.ToString("X8"); case int n: // 判断类型,这里 n 肯定 <= 0xffff return n.ToString("X4"); case bool b: return b ? "ON" : "OFF"; case null: return null; default: return v.ToString(); } }
注意一下上面第一个分支中 when
的用法就好了。
ref 局部变量和 ref 返回值
这已经是很接近 C/C++ 的一种用法了。虽然官方说法是这样做可以解决一些安全性问题,但我个人目前还是没遇到它的使用场景。如果设计足够好,在目前又加入了元组新特性和解构的情况下,个人认为几乎可以避免使用 out
和 ref
。
既然没用到,我也不多说了,有用到的同学来讨论一下!
数字字面量语法增强
这里有两点增强,一点是引入了 0b
前缀的二进制数字面量语法,另一点是可以在数值字面量中任意使用 _
对数字进行分组。这个不用多数,举两个例就明白了
const int MARK_THREE = 0b11; // 0x03 const int LONG_MARK = 0b_1111_1111; // 0xff const double PI = 3.14_1592_6536
局部函数
经常写 JavaScript 的同学肯定会深有体会,局部函数是个好东西。当然它在 C# 中带来的最大好处是将某些代码组织在了一起。我之前在项目中大量使用了 Lambda 来代替局部函数,现在可以直接替换成局部函数了。Labmda 和局部函数虽然多数情况下能做同样的事情,但是它们仍然有一些区别
对于 Lambda,编译器要干的事情比较多。总之呢,就是编译效率要低得多
Lambda 通过委托实现,调用过程比较复杂,局部函数可以直接调用。简单地说就是局部函数执行效率更高
Lambda 必须先定义再使用,局部函数可以定义在使用之后。据说这在对递归算法的支持上会有区别
比较常用的地方是 Enumerator 函数和 async 函数中,因为它们实际都不是立即执行的。
我在项目中多是用来组织代码。局部函数代替只被某一个公共 API 调用的私有函数来组织代码虽然不失为一个简化类结构的好方法,但是把公共 API 函数的函数体拉长。所以很多时候我也会使用内部类来代替某些私有函数来组织代码。这里顺便说一句,我不赞成使用 #region
组织代码。
支持更多 async 返回类型
如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比较像 <code>Promise
的角色。不用羡慕 JavaScript 的 async 支持 Promise like,现在 C# 的 async 也支持 Task like 了,只要实现了 GetAwaiter
方法就行。
官方提供了一个 ValueTask
作为示例,可以通过 NuGet 引入:
Install-Package System.Threading.Tasks.Extensions
这个 ValueTask
比较有用的一点就是兼容了数据类型和 Task:
string cache; ValueTask<string> GetData() { return cache == null ? new ValueTask<string>(cache) : new ValueTask<string>(GetRemoteData()); // 局部函数 async Task<string> GetRemoteData() { await Task.Delay(100); return "hello async"; } }
以上是C# 7.0 的新特性(速览版)的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

PHP8.3发布:新特性一览随着技术的不断发展和需求的不断变化,编程语言也在不断更新和改进。作为一种广泛应用于网络开发的脚本语言,PHP一直在不断进步,为开发者提供更强大和高效的工具。最近发布的PHP8.3版本带来了许多期待已久的新特性和改进,下面让我们来看一下这些新特性的一览。非空属性的初始化在过去的PHP版本中,如果一个类的属性没有被明确赋值,它的值

深入解析PHP8的新特性,助您掌握最新技术随着时间的推移,PHP编程语言一直在不断演进和改进。最近发布的PHP8版本为开发者提供了许多令人兴奋的新特性和改进,为我们的开发工作带来了更多便利和效率。在本文中,我们将深入解析PHP8的新特性,并提供具体的代码示例,旨在帮助您更好地掌握这些最新的技术。JIT编译器PHP8引入了JIT(Just-In-Time)编

php8新特性有JIT 编译器、类型推导、命名参数、联合类型、属性、错误处理改进、异步编程支持、新的标准库函数和匿名类的扩展等。详细介绍:1、JIT编译器,PHP8引入了JIT编译器,这是一个重要的性能改进,JIT编译器可以对一些高频执行的代码进行实时编译和优化,从而提高运行速度;2、类型推导,PHP8引入了类型推导功能,允许开发者在声明变量时自动推导出变量的类型等等。

【Go语言新特性解读:让编程更高效,需要具体代码示例】近年来,Go语言在软件开发领域备受关注,其简洁、高效的设计理念吸引了越来越多的开发者。作为一种静态类型的编程语言,Go语言不断推出新的特性以提高开发效率,简化代码编写过程。本文将深入解读Go语言最新的特性,探讨如何通过具体的代码示例来体验这些新特性带来的便利。模块化开发(GoModules)Go语言从1

CSS3的新特性一览:如何使用CSS3实现过渡效果CSS3作为CSS的最新版本,在众多新特性中,最有趣和实用的应该是过渡效果(transition)。过渡效果可以让我们的页面在交互时更加平滑、漂亮,给用户带来良好的视觉体验。本文将介绍CSS3过渡效果的基本用法,并附带相应的代码示例。transition-property属性:指定需要过渡的CSS属性过渡效果

CSS3的新特性一览:如何使用CSS3实现水平居中布局在网页设计和布局中,水平居中布局是一项常见的需求。过去,我们经常使用复杂的JavaScript或CSS技巧实现此目的。然而,CSS3引入了一些新的特性,使得水平居中布局更加简单和灵活。本文将介绍一些CSS3的新特性,并提供一些代码示例,演示如何使用CSS3实现水平居中布局。一、使用flexbox布局fle

PHP8.1引入的新的Redis扩展随着互联网的快速发展,大量的数据需要进行存储和处理。为了提高数据处理的效率和性能,缓存成为了一个不可或缺的部分。而在PHP开发中,Redis作为一种高性能的键值对存储系统,被广泛应用于缓存和数据存储的场景。为了进一步提升Redis在PHP中的使用体验,PHP8.1引入了新的Redis扩展,本文将介绍这一扩展的新增功能,并给

CSS3的新特性一览:如何应用CSS3动画效果引言:随着互联网的发展,CSS3逐渐取代了CSS2成为前端开发中最常用的样式语言。CSS3提供了许多新的特性,其中最受欢迎的是动画效果。通过使用CSS3动画,可以为网页添加令人惊艳的交互效果,提高用户体验。本文将介绍一些CSS3常用的动画特性,并提供相关的代码示例。一、过渡动画(TransitionAnimat
