前言

开头防杠:.NET 的基础库、语言、运行时团队从来都是相互独立各自更新的,.NET 6 在基础库、运行时上同样做了非常多的改进,不过本文仅仅介绍语言部分。

距离上次介绍 C# 10 的特性已经有一段时间了,伴随着 .NET 6 的开发进入尾声,C# 10 最终的特性也终于敲定了。总的来说 C# 10 的更新内容很多,并且对类型系统做了不小的改动,解决了非常多现有的痛点。

从 C# 10 可以看到一个消息,那就是 C# 语言团队开始主要着重于改进类型系统和功能性方面的东西,而不是像以前那样热衷于各种语法糖了。C# 10 只是这个旅程的开头,后面的 C# 11 、12 将会有更多关于类型系统的改进,使其拥有强如 Haskell 、Rust 的表达能力,不仅能提供从头到尾的跨程序集的静态类型支持,还能做到像动态类型语言那样的灵活。逻辑代码是类型的证明,只有类型系统强大了,代码编写起来才能更顺畅、更不容易出错。

record struct

首先自然是 record struct,解决了 record 只能给 class 而不能给 struct 用的问题:

record struct Point(int X, int Y);

用 record 定义 struct 的好处其实有很多,例如你无需重写 GetHashCodeEquals 之类的方法了。

sealed record ToString 方法

之前 record 的 ToString 是不能修饰为 sealed 的,因此如果你继承了一个 record,相应的 ToString 行为也会被改变,因此这是个虚方法。

但是现在你可以把 record 里的 ToString 方法标记成 sealed,这样你的 ToString 方法就不会被重写了。

struct 无参构造函数

一直以来 struct 不支持无参构造函数,现在支持了:

struct Foo
{
public int X;
public Foo() { X = 1; }
}

但是使用的时候就要注意了,因为无参构造函数的存在使得 new struct()default(struct) 的语义不一样了,例如 new Foo().X == default(Foo).X 在上面这个例子中将会得出 false

匿名对象的 with

可以用 with 来根据已有的匿名对象创建新的匿名对象了:

var x = new { A = 1, B = 2 };
var y = x with { A = 3 };

这里 y.A 将会是 3 。

全局的 using

利用全局 using 可以给整个项目启用 usings,不再需要每个文件都写一份。比如你可以创建一个 Import.cs,然后里面写:

using System;
using i32 = System.Int32;

然后你整个项目都无需再 using System,并且可以用 i32 了。

文件范围的 namespace

这个比较简单,以前写 namespace 还得带一层大括号,以后如果一个文件里只有一个 namespace 的话,那直接在最上面这样写就行了:

namespace MyNamespace;

常量字符串插值

你可以给 const string 使用字符串插值了,非常方便:

const string x = "hello";
const string y = $"{x}, world!";

lambda 改进

这个改进可以说是非常大,我分多点介绍。

1. 支持 attributes

lambda 可以带 attribute 了:

f = [Foo] (x) => x; // 给 lambda 设置
f = [return: Foo] (x) => x; // 给 lambda 返回值设置
f = ([Foo] x) => x; // 给 lambda 参数设置

2. 支持指定返回值类型

此前 C# 的 lambda 返回值类型靠推导,C# 10 开始允许在参数列表最前面显示指定 lambda 类型了:

f = int () => 4;

3. 支持 ref 、in 、out 等修饰

f = ref int (ref int x) => ref x; // 返回一个参数的引用

4. 头等函数

函数可以隐式转换到 delegate,于是函数上升至头等函数:

void Foo() { Console.WriteLine("hello"); }
var x = Foo;
x(); // hello

5. 自然委托类型

lambda 现在会自动创建自然委托类型,于是不再需要写出类型了。

var f = () => 1; // Func<int>
var g = string (int x, string y) => $"{y}{x}"; // Func<int, string, string>
var h = "test".GetHashCode; // Func<int>

CallerArgumentExpression

现在,CallerArgumentExpression 这个 attribute 终于有用了。借助这个 attribute,编译器会自动填充调用参数的表达式字符串,例如:

void Foo(int value, [CallerArgumentExpression("value")] string? expression = null)
{
Console.WriteLine(expression + " = " + value);
}

当你调用 Foo(4 + 5) 时,会输出 4 + 5 = 9。这对测试框架极其有用,因为你可以输出 assert 的原表达式了:

static void Assert(bool value, [CallerArgumentExpression("value")] string? expr = null)
{
if (!value) throw new AssertFailureException(expr);
}

tuple 支持混合定义和使用

比如:

int y = 0;
(var x, y, var z) = (1, 2, 3);

于是 y 就变成 2 了,同时还创建了两个变量 x 和 z,分别是 1 和 3 。

接口支持抽象静态方法

这个特性将会在 .NET 6 作为 preview 特性放出,意味着默认是不启用的,需要设置 <LangVersion>preview</LangVersion><EnablePreviewFeatures>true</EnablePreviewFeatures>,然后引入一个官方的 nuget 包 System.Runtime.Experimental 来启用。

然后接口就可以声明抽象静态成员了,.NET 的类型系统正式具备虚静态方法分发能力。

例如,你想定义一个可加而且有零的接口 IMonoid<T>

interface IMonoid<T> where T : IMonoid<T>
{
abstract static T Zero { get; }
abstract static T operator+(T l, T r);
}

然后可以对其进行实现,例如这里的 MyInt:

public class MyInt : IMonoid<MyInt>
{
public MyInt(int val) { Value = val; } public static MyInt Zero { get; } = new MyInt(0);
public static MyInt operator+(MyInt l, MyInt r) => new MyInt(l.Value + r.Value); public int Value { get; }
}

然后就能写出一个方法对 IMoniod<T> 进行求和了,这里为了方便写成扩展方法:

public static class IMonoidExtensions
{
public static T Sum<T>(this IEnumerable<T> t) where T : IMonoid<T>
{
var result = T.Zero;
foreach (var i in t) result += i;
return result;
}
}

最后调用:

List<MyInt> list = new() { new(1), new(2), new(3) };
Console.WriteLine(list.Sum().Value); // 6

你可能会问为什么要引入一个 System.Runtime.Experimental,因为这个包里面包含了 .NET 基础类型的改进:给所有的基础类型都实现了相应的接口,比如给数值类型都实现了 INumber<T>,给可以加的东西都实现了 IAdditionOperators<TLeft, TRight, TResult> 等等,用起来将会非常方便,比如你想写一个函数,这个函数用来把能相加的东西加起来:

T Add<T>(T left, T right) where T : IAdditionOperators<T, T, T>
{
return left + right;
}

就搞定了。

接口的静态抽象方法支持和未来 C# 将会加入的 shape 特性是相辅相成的,届时 C# 将利用 interface 和 shape 支持 Haskell 的 class、Rust 的 trait 那样的 type classes,将类型系统上升到一个新的层次。

泛型 attribute

是的你没有看错,C# 的 attributes 支持泛型了:

class TestAttribute<T> : Attribute
{
public T Data { get; }
public TestAttribute(T data) { Data = data; }
}

然后你就能这么用了:

[Test<int>(3)]
[Test<float>(4.5f)]
[Test<string>("hello")]

允许在方法上指定 AsyncMethodBuilder

C# 10 将允许方法上使用 [AsyncMethodBuilder(...)] 来使用你自己实现的 async method builder,代替自带的 Task 或者 ValueTask 的异步方法构造器。这也有助于你自己实现零开销的异步方法。

line 指示器支持行列和范围

以前 #line 只能用来指定一个文件中的某一行,现在可以指定行列和范围了,这对写编译器和代码生成器的人非常有用:

#line (startLine, startChar) - (endLine, endChar) charOffset "fileName"

// 比如 #line (1, 1) - (2, 2) 3 "test.cs"

嵌套属性模式匹配改进

以前在匹配嵌套属性的时候需要这么写:

if (a is { X: { Y: { Z: 4 } } }) { ... }

现在只需要简单的:

if (a is { X.Y.Z: 4 }) { ... }

就可以了。

改进的字符串插值

以前 C# 的字符串插值是很粗暴的 string.Format,并且对于值类型参数来说会直接装箱,对于多个参数而言还会因此而分配一个数组(比如 string.Format("{} {}", a, b) 其实是 string.Format("{} {}", new object [] { (object)a, (object)b })),这很影响性能。现在字符串插值被改进了:

var x = 1;
Console.WriteLine($"hello, {x}");

会被编译成:

int x = 1;
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(7, 1);
defaultInterpolatedStringHandler.AppendLiteral("hello, ");
defaultInterpolatedStringHandler.AppendFormatted(x);
Console.WriteLine(defaultInterpolatedStringHandler.ToStringAndClear());

上面这个 DefaultInterpolatedStringHandler 也可以借助 InterpolatedStringHandler 这个 attribute 替换成你自己实现的插值处理器,来决定要怎么进行插值。借助这些可以实现接近零开销的字符串插值。

Source Generator v2

代码生成器在 C# 10 将会迎来 v2 版本,这个版本包含很多改进,包括强类型的代码构建器,以及增量编译的支持等等。

C# 10 完整特性介绍的更多相关文章

  1. spark2.0的10个特性介绍

    1. Spark 2.0 ! 还记得我们的第七篇 Spark 博文里吗?里面我用三点来总结 spark dataframe 的好处: 当时是主要介绍 spark 里的 dataframe,今天是想总结 ...

  2. Java 11 新特性介绍

    Java 11 已于 2018 年 9 月 25 日正式发布,之前在Java 10 新特性介绍中介绍过,为了加快的版本迭代.跟进社区反馈,Java 的版本发布周期调整为每六个月一次——即每半年发布一个 ...

  3. Java 12 新特性介绍,快来补一补

    Java 12 早在 2019 年 3 月 19 日发布,它不是一个长久支持(LTS)版本.在这之前我们已经介绍过其他版本的新特性,如果需要可以点击下面的链接进行阅读. Java 11 新特性介绍 J ...

  4. ArcGIS 10.3 for Desktop新特性介绍

    ArcGIS 10.3是一个完整公布的ArcGIS平台,它包含新的产品(ArcGIS Pro),针对10.2版本号产品进行了功能增强和稳定性的改进. ArcGIS 10.3 for Server新特性 ...

  5. Hadoop3.0新特性介绍,比Spark快10倍的Hadoop3.0新特性

    Hadoop3.0新特性介绍,比Spark快10倍的Hadoop3.0新特性 Apache hadoop 项目组最新消息,hadoop3.x以后将会调整方案架构,将Mapreduce 基于内存+io+ ...

  6. Xcode9新特性介绍-中文篇

    背景: Xcode 9 新特性介绍: 1.官方原文介绍链接 2.Xcode9 be ta 2 官方下载链接 本文为官方介绍翻译而来,布局排版等都是按照官方布局来的. 与原文相比,排版上基本还是熟悉的配 ...

  7. .Net Core 2.0生态(3):ASP.NET Core 2.0 特性介绍和使用指南

    ASP.NET Core 2.0 发布日期:2017年8月14日 ASP.NET团队宣布ASP.NET Core 2.0正式发布,发布Visual Studio 2017 15.3支持ASP.NET ...

  8. GrapeCity Documents for Excel 文档API组件 V2.2 新特性介绍

    GrapeCity Documents for Excel 文档API组件 V2.2 正式发布,本次新版本包含诸多重量级产品功能,如:将带有形状的电子表格导出为 PDF.控制分页和电子表格内容.将Ex ...

  9. dubbox新特性介绍

    dubbx是当当网对原阿里dubbo2.x的升级,并且兼容原有的dubbox.其中升级了zookeeper和spring版本,并且支持restfull风格的远程调用. dubbox git地址:  h ...

随机推荐

  1. 数学:3D和矩阵

    跟紧工作需求学习,于是抽了点时间看了看用于2D3D转换的矩阵内容. 矩阵在3D数学中,可以用来描述两个坐标系间 的关系,通过定义的运算能够把一个坐标系中的向量转换到另一个坐标系中.在线性代数中,矩阵就 ...

  2. QPainter::begin: Paint device returned engine == 0, type: 1

    开始 使用QPainter画图,需要继承QWidget,重写paintEvent()虚函数,在里面进行绘图. 或者可以考虑绘制到QImage或者QPixmap上面,然后在paintEvent()里面调 ...

  3. .obj : error LNK2019: 无法解析的外部符号

    记录一个报错 .obj : error LNK2019: 无法解析的外部符号 "public: void __thiscall 习惯上先去看看 | "#include"语 ...

  4. 接口自动化框架搭建Unittes+HTMLTestRunner

    本次主要尝试搭建接口自动化框架,基于 unittest+HTMLTestRunner 框架主要模块: config: 存放配置文件 lib: 封装了一些接口前置函数:处理各种事物 log: 存放生成的 ...

  5. Java 内存泄漏知多少?

    先看再点赞,给自己一点思考的时间,如果对自己有帮助,微信搜索[程序职场]关注这个执着的职场程序员.我有什么:职场规划指导,技能提升方法,讲不完的职场故事,个人成长经验. 面试的时候内存管理是不是很多面 ...

  6. WPF技巧:通过代码片段管理器编写自己常用的代码模板提示效率

    在写自定义控件的时候,有一部分功能是当内部的值发生变化时,需要通知控件的使用者,而当我在写依赖项属性的时候,我可以通过popdp对应的代码模板来完成对应的代码,但是当我来写属性更改回调的时候,却发现没 ...

  7. postgresql行列转换

    --安装扩展 CREATE EXTENSION tablefunc --使用CROSSTAB函数 SELECT * FROM CROSSTAB('SELECT 主键, 需转换的列名, 需转换的列值 F ...

  8. MQTT介绍与使用(转载)

    物联网是新一代信息技术的重要组成部分,也是"信息化"时代的重要发展阶段.其英文名称是:"Internet of things(IoT)".顾名思义,物联网就是物 ...

  9. vmware使用U盘安装系统

    创建好系统 创建一个新的硬盘,选择"physicalDrive1" 如果识别不到physicalDrive 1,使用下面的方法. 1.在本机的服务里面启用下面的服务. 2.重启 V ...

  10. Django基础013--redis开发

    1.redis配置 在settings.py中加入以下代码块,可支持多个redis的配置 1 CACHES = { 2 "default": { 3 "BACKEND&q ...