C# 中对重返 .NET 的开发人员来说非常棒的新特性
上篇推送中推荐了免费参加Azure Fundamentals的机会,不知道大家有没有参与呢?我已经顺利拿到微软赠送的考试券并通过考试了:
其实这是微软Azure认证体系中的最基础的一个认证,考试范围涵盖了云计算的基本原则、Azure的核心服务、Azure的安全策略、成本及优化支出等。基本只是一个理论概览,并不算难,准备一两周即可过关。目前正在准备 Azure Solutions Architect Expert 的考试,欢迎有兴趣考证的同学一起交流。
如果上次错过培训的话,不用担心,这里还有一次机会:
如上次一样,参加者可以得到微软赠送的免费考试券用于参加AZ-900考试。链接可见:https://mktoevents.com/Microsoft+Event/184203/157-GQE-382 或点击文末阅读原文链接。
推荐大家趁送考试券的机会考一个。反正免费呗
今天分享一篇翻译的文章:
C# 中对重返 .NET 的开发人员来说非常棒的新特性
原文链接:Great new features in C# for a returning .NET dev, deconstruct, pattern matching and records[1] by Chris Noring
请订阅我的Twitter[2],非常乐意接受您的建议或改进 /Chris
我离开 .NET 开发已经有一段时间了。实际上我一直在从事全栈 JavaScript 开发,当重新关注 .NET 领域后,我注意到 C# 语言已经添加了一些惊人的功能,以及一些我可以从 JavaScript 和函数式语言中识别出来的特性。这让我非常高兴,因为这意味着 C# 能够从不同的范式及最重要的演化中选择最优秀的部分,变得越来越好。在2000年年初的时候,谁会想到 .NET 现在可以运行在所有平台上?:)
那么,这些功能有哪些呢?
参考资料
-
解构, 元组和对象 [3] 该文档涵盖了如何对元组和对象进行解构。它包含了您需要了解的所有内容 -
C# 8.0 中的模式匹配 [4] C# 8.0 附带了对 C# 7.0 中添加的模式匹配的一些更新。 -
模式匹配概述 [5] 该文档涵盖了现有的不同类型的模式匹配。 -
Records - 记录初探 [6]
1 Deconstruction - 解构
这是我在 JavaScript 中很喜欢的一个功能,当然在 JavaScript 中它被称为 destructuring。这是什么意思呢?这个想法来自于当你要引用一个对象或一个列表中的项的一些属性,但又不想要全部的属性。让我们来看一个 JavaScript 的例子:
// JavaScript example
function getPerson() {
return {
name: 'chris',
company: 'Microsoft',
title: 'Cloud Advocate'
};
}
// without destructuring
const person = getPerson();
console.log(person.name);
console.log(person.company);
// with destructuring
const { name, company } = getPerson();
console.log(name);
console.log(company);
你可以看到,当没有使用 destructuring 的时候,即使我只想获取一个成员,也必须键入person
。很多多余的字符 :/
怎样在 C# 中使用呢?
(var pname, var pcompany) = CreatePerson("chris", "microsoft")
好吧,看起来类似。
CreatePerson()
返回的到底是什么呢?
在这种情况下,它返回一个对象,但也可能是一个元组。
这样就行了吗?
这取决于你正在处理什么结构。它目前适用于元组和类,尽管支持类的话你需要做一些额外的工作将它运行起来。
Tuple - 元组
让我们首先通过元组来了解它的来龙去脉。首先创建一个可生成元组的方法。如下所示:
public static (string, string) GetPerson(string name, string company)
{
return (name, company);
}
我们可以通过两种方式进行此操作:
-
从元组访问所有变量 -
仅访问我们所需要的变量但是使用弃元符号 _
译者注:从 C# 7.0 开始,C# 支持弃元,用
_
表示。弃元相当于未赋值的变量,无需为该变量分配存储空间,所以可以减少内存分配。本质上来说这是一种人为取消使用的临时虚拟变量。参考弃元[7]
让我们演示第一种情况:
所有变量
var (name, company) = GetPerson("Chris", "Microsoft");
// name = "Chris"
// company = "Microsoft"
使用弃元
var (name, _) = GetPerson("Chris", "Microsoft");
// name = "Chris"
在上面的例子中,我们使用 _
表示我们不关心元组中的第二个参数。
那么如果我们有3个或多个参数,他们是否都被储存在
_
里?
并不是。您需要声明每个参数,但我们不关心位置,因此可以像这样输入:
var (name, _, _) = GetPerson("Chris", "Microsoft");
Classes - 类
像元组一样,我们可以对类进行解构来访问特定的字段。为了实现这个目的,我们需要将一个 Deconstruct()
方法添加到类中,如下所示:
public class Person
{
public string Name { get; set; }
public string Company { get; set; }
public string Country { get; set; }
public void Deconstruct(out string name, out string company)
{
name = Name;
company = Company;
}
public void Deconstruct(out string name, out string company, out string country)
{
name = Name;
company = Company;
country = Country;
}
}
// 用如下方式访问
(var name, var company) = new Person(){ Name = "Chris", Company ="Microsoft" };
对于我们不关心的参数,可以使用同样的规则,即使用弃元 _
,如下所示:
(var name, var company, _) = new Person(){ Name = "Chris", Company ="Microsoft" };
上面的例子中,我们丢弃了 country
。
重载解构方法
您可能已经注意到在 Person
类中有两个 Deconstruct()
方法。这是因为我们使用了重载,所以可以有以下两种方式:
(var name, var company, _) = new Person(){ Name = "Chris", Company ="Microsoft" };
第二个示例:
(var name, var company) = new Person(){ Name = "Chris", Company ="Microsoft" };
在第二个示例中,我们实际上移除了弃元 _
,因为我们正在使用带有两个参数的 Deconstruct()
方法。
但是,我什么时候需要弃元
_
呢?
在以下的例子中,你仅需要它的一个参数:
(var name, _ ) = new Person(){ Name = "Chris", Company ="Microsoft" };
这里我们在使用带有两个参数的 Deconstruct()
方法,但是丢弃了最后一个参数。
那么,我为什么不直接输入
( var name )
呢?
我只能告诉你它就是这样实现的。只要记住,如果使用重载,需要2个或更多参数来使用它。
要了解更多关于解构的内容,请阅读以下文档:
https://docs.microsoft.com/en-us/dotnet/csharp/deconstruct
2 Pattern matching - 模式匹配
我在像 F# 的函数式语言中见过模式匹配。它的目的是什么?如果你问我的话,我会说这是为了减少输入并在真正要进行比较时更精确。在使用 Switch 的情况下,如何使这些更容易阅读和更有针对性。
请展示
好。当我过去使用 C# 时,switch 支持字符串、整数和枚举,仅此而已。当使用对象时 switch 并不合适,我需要使用 if、 else-if 来转换对象。让我们先展示一些代码:
public abstract class Character
{
private string _name;
public string Name
{
get {
return _name;
}
}
public Character(string name)
{
_name = name;
}
}
public class Hero: Character
{
public Hero(string name): base("The Good" + name){}
public string Healing()
{
return "Performs healing";
}
}
public class Villain: Character
{
public Villain(string name) : base("The Evil "+ name) { }
public string Lightning()
{
return "Performs Force lightning";
}
}
现在假设我们有如下代码:
var hero = new Hero("Luke");
var villain = new Villain("Darth Vader");
Character character = hero;
var ability = string.Empty;
现在,让我们加入一些我曾经在 C# 中用过的 switch 逻辑:
if(character is Hero)
{
var h = (Hero) character;
ability = h.Healing();
} else {
var v = (Villain) character;
ability = v.Lightning();
}
我们对这种用法满意吗?其实并不。好消息是,从 C# 7 起,我们可以这样做:
switch(character)
{
case Hero hero1:
ability = hero1.Healing();
break;
case Villain villain1:
ability = villain1.Lightning();
break;
}
在上面的示例中,我们根据条件创建了 hero1
或 villain1
对象。看起来少输入了一些代码。
让我们看一下在 C# 8.0 中怎么做:
ability = character switch {
Hero hero2 => hero2.Healing(),
Villain villain2 => villain2.Lightning(),
_ => "Unknown"
};
这看起来真的不错,非常紧凑 :)
对吧?
Positional pattern - 位置模式
C# 8 确实令人惊叹,或者至少对于我来说 :) 那么还有其他的吗?我们把它叫做位置模式。这种模式可以让我们检查一个对象,根据它对哪个属性设置的特定的值进行相应的处理。
假设我们有一个 Invoice 类。根据它的状态,我们可能要以不同的方式处理它,那么我们如何在给定的模式匹配中表示呢?
我们开始之前,先说一下我们将使用 Destruction 做到这一点。那么接下来是你可能感兴趣的东西。假设我们在一个 Invoice
类中有 BossSigned
和 SkipLevelBossSigned
属性。我们制定了一条业务规则,如果老板和更上级的老板已经签署发票,则可以继续处理。
-
BossSigned
, 值为null
或签署的日期 -
SkipLevelBossSigned
, 值为null
或签署的日期
var result = invoice switch
{
Invoice (null, null) => "Boss need to sign",
Invoice (_ , null) => "Skip level boss need to sign",
_ => "All good, process"
}
Property pattern - 属性模式
到目前为止,我们为 Invoice
的类型进行了匹配,但我们也可以不用关心类型,只需要匹配属性:
var result = shape switch
{
{ BossSigned: null, SkipLevelBossSigned: null } => "Boss need to sign",
{ SkipLevelBossSigned: null } => "Skip level boss need to sign",
_ => "All good, process"
}
上面的示例其实有个潜在的风险,如果 _
具有 null 值它会匹配到默认的case。在生产环境中使用之前您可能需要修复该问题。
3 Records - 记录
这个特性还没有正式发布,但我感到很兴奋。简而言之,我们可以忘掉字段声明
你是认真的吗?那要用什么代替呢?看起来会是什么样子?
这个特性对我来说有点类似 Scala 和 TypeScript,它可以为您完成繁重的工作,即创建字段。在 TypeScript 中看起来像这样:
class Hero {
constructor(private name: String, private hp: number, public shout: String)
}
上面的示例中为您创建了 name
,hp
和 shout
。所以无需像这样输入:
// the above would compile to this
class Hero {
constructor(name: String, hp: number, shout: String) {
this.name = name;
this.hp = hp;
this.shout = shout;
}
}
作为 C# 开发人员,这看起来有点倒退,类型放在最后?
这让我想起了这个说法,但是的,就是这样 :)
如果在 C# 实现将会是什么样?他们正在努力实现它,但提案应该是这样的:
public class Hero(String name, int hp, String shout);
以上代码将会被编译成这样:
public class Hero : IEquatable<Hero>
{
public String name;
public int hp;
public String shout;
public Hero(string name, int hp, string shout)
{
this.name = name;
this.hp = hp;
this.shout = shout;
}
public bool Equals(Hero other)
{
//
}
public override bool Equals(object other)
{
//
}
public override int GetHashCode()
{
//
}
public void Deconstruct(out string name, out int hp, out string shout)
{
name = name;
hp = hp;
shout = shout;
}
public Hero With(string name = this.name, int age = this.age, string shout = this.shout) => new Hero(name, hp, shout);
}
所以,我们会得到很多额外的益处,如 Deconstruct、Equal,并且能使用新的语法 With
。With
也是一个新的东西,可以帮助我们创建新的不可变对象。我们能够像这样输入:
var hero = GetHero(); // get a Hero object from somewhere
var newHero = hero.With(hp = 23);
// alt syntax
var otherHero = hero with { hp = 23 };
小结
这就是本文的结尾了。如果你像我一样重返 .NET 开发,也许有些感受是相同的或不同的,哦,对了还有 .NET Core,当然,这是个大新闻。能够在 Linux / Mac上运行 .NET,对代码进行容器化等等。谁会想到这一天真的来临?
坦白说,我会使用这些结构吗?我肯定会使用模式匹配,即使我在很多时候都采用继承并避免这种逻辑。
那么解构呢?对元组来说我会使用,无需额外的工作就可以使用它。那么对象呢?我看到我需要做以下两件事之一:
-
在一个类上创建一个方法返回所需的元组 -
在有必要的类上添加 Deconstruct()
方法。我会问自己以下问题:我是否需要将字段从类传递到外部环境?字段是否提供了实现细节?属性呢?我们还有包含很多公共字段的DTO对象,因此……
记录,我需要它们。迫不及待想看到它们的实现了 :)
参考资料
Great new features in C# for a returning .NET dev, deconstruct, pattern matching and records: https://softchris.github.io/pages/dotnet-csharp-news.html
[2]Twitter: https://twitter.com/chris_noring
[3]解构, 元组和对象: https://docs.microsoft.com/en-us/dotnet/csharp/deconstruct?wt.mc_id=devto-blog-chnoring
[4]C# 8.0 中的模式匹配: https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/may/csharp-8-0-pattern-matching-in-csharp-8-0?wt.mc_id=devto-blog-chnoring
[5]模式匹配概述: https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching?wt.mc_id=devto-blog-chnoring
[6]Records - 记录初探: https://blog.cdemi.io/whats-coming-in-c-8-0-records/
[7]弃元: https://docs.microsoft.com/zh-cn/dotnet/csharp/discards
👇👇👇