• 技术文章 >后端开发 >C#.Net教程

    C# 7.0 的新特性(速览版)

    巴扎黑巴扎黑2017-04-15 09:05:55原创1270
    《〔译〕 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 的朋友一定对其 Item1Item2 这样毫无语义的命名深感不爽。不过 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);
    }

    调用这个方法可以获得元组,因为定义的时候返回值指定了每个数据成员的名称,所以从元组获取数据可以是语义化的,当然仍然可以使用 Item1Item2

    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);

    模式匹配

    模式匹配目前支持 isswitch。说起来挺高大上的一个名字,换个接地气一点的说法就是判断类型顺便定义个具体类型的引用,有兴趣还可以加再点额外的判断。

    对于 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++ 的一种用法了。虽然官方说法是这样做可以解决一些安全性问题,但我个人目前还是没遇到它的使用场景。如果设计足够好,在目前又加入了元组新特性和解构的情况下,个人认为几乎可以避免使用 outref

    既然没用到,我也不多说了,有用到的同学来讨论一下!

    数字字面量语法增强

    这里有两点增强,一点是引入了 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 和局部函数虽然多数情况下能做同样的事情,但是它们仍然有一些区别

    比较常用的地方是 Enumerator 函数和 async 函数中,因为它们实际都不是立即执行的。

    我在项目中多是用来组织代码。局部函数代替只被某一个公共 API 调用的私有函数来组织代码虽然不失为一个简化类结构的好方法,但是把公共 API 函数的函数体拉长。所以很多时候我也会使用内部类来代替某些私有函数来组织代码。这里顺便说一句,我不赞成使用 #region 组织代码。

    支持更多 async 返回类型

    如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比较像 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 的朋友一定对其 Item1Item2 这样毫无语义的命名深感不爽。不过 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);
    }

    调用这个方法可以获得元组,因为定义的时候返回值指定了每个数据成员的名称,所以从元组获取数据可以是语义化的,当然仍然可以使用 Item1Item2

    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);

    模式匹配

    模式匹配目前支持 isswitch。说起来挺高大上的一个名字,换个接地气一点的说法就是判断类型顺便定义个具体类型的引用,有兴趣还可以加再点额外的判断。

    对于 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++ 的一种用法了。虽然官方说法是这样做可以解决一些安全性问题,但我个人目前还是没遇到它的使用场景。如果设计足够好,在目前又加入了元组新特性和解构的情况下,个人认为几乎可以避免使用 outref

    既然没用到,我也不多说了,有用到的同学来讨论一下!

    数字字面量语法增强

    这里有两点增强,一点是引入了 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 和局部函数虽然多数情况下能做同样的事情,但是它们仍然有一些区别

    比较常用的地方是 Enumerator 函数和 async 函数中,因为它们实际都不是立即执行的。

    我在项目中多是用来组织代码。局部函数代替只被某一个公共 API 调用的私有函数来组织代码虽然不失为一个简化类结构的好方法,但是把公共 API 函数的函数体拉长。所以很多时候我也会使用内部类来代替某些私有函数来组织代码。这里顺便说一句,我不赞成使用 #region 组织代码。

    支持更多 async 返回类型

    如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比较像 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";
        }
    }



    php入门到就业线上直播课:查看学习

    以上就是C# 7.0 的新特性(速览版)的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    专题推荐:c#7.0,新特性
    上一篇:C#中BackgroundWorker用法的详解(图) 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• c语言中源文件编译后生成什么文件• c语言本身有没有输入输出语句• c语言中的标识符是由什么组成• c语言中关键字有多少个• c语言中case是什么意思
    1/1

    PHP中文网