C# 9.0中引入的新特性init和record的使用思考

.NET 5.0已经发布,C# 9.0也为我们带来了许多新特性,其中最让我印象深刻的就是init和record type,很多文章已经把这两个新特性讨论的差不多了,本文不再详细讨论,而是通过使用角度来思考这两个特性。

init

init是C# 9.0中引入的新的访问器,它允许被修饰的属性在对象初始化的时候被赋值,其他场景作为只读属性的存在。直接使用的话,可能感受不到init的意义,所以我们先看看之前是如何设置属性为只读的。

private set设置属性为只读

设置只读属性有很多种方式,本文基于private set来讨论。
首先声明一个产品类,如下代码所示,我们把Id设置成了只读,这个时候也就只能通过构造函数来赋值了。在通常情况下,实体的唯一标识是不可更改的,同时也要防止Id被意外更改。

public class Product
{public Product(int id){this.Id = id;}public int Id { get; private set; }//public int Id { get; }public string ProductName { get; set; }public string Description { get; set; }
}class Program
{static void Main(string[] args){Product product = new Product(1){ProductName = "test001",Description = "Just a description"};Console.WriteLine($"Current Product Id: {product.Id},\n\rProduct Name: {product.ProductName}, \n\rProduct Description: {product.Description}");//运行结果//Current Product Id: 1,//Product Name: test001,//Product Description: Just a descriptionConsole.ReadKey();}
}

record方式设置只读

使用init方式,是非常简单的,只需要把private set改成init就行了:

public int Id { get; init; }

两者比较

为了方便比较,我们可以将ProductName设置成了private set,然后通过ILSpy来查看一下编译后的代码,看看编译后的Id和ProductName有何不同咋一看,貌似没啥区别,都使用到了initonly来修饰。但是如果仅仅只是替换声明方式,那么这个新特性似乎就没有什么意义了。
接下来我们看第二张图:如图标记的那样,区别还是很明显的,通过init修饰的属性并没有完全替换掉set,由此看来微软在设计init的时候,还是挺用心思的,也为后面的赋值留下了入口。

instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Id (int32 'value')

另外在赋值的时候,使用private set修饰的属性,需要定义构造函数,通过构造函数赋值。而使用了init修饰的属性,则不需要定义构造函数,直接在对象初始化器中赋值即可。

Product product = new Product
{Id = 1,ProductName = "test001",Description = "Just a description"
};product.Id = 2;//Error CS8852 Init-only property or indexer 'Product.Id' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

如上代码所示,只读属性Id的赋值并没有在构造函数中赋值,毕竟当一个类的只读字段十分多的时候,构造函数也变得复杂。而且在赋值好之后,无法修改,这和我们对只读属性在通常情况下的理解是一致的。另外通过init修饰的好处便是省却了一部分只读属性在操作上的复杂性,使得对象的声明与赋值更加直观。
在合适的场景下选择最好的编程方式,是程序员的一贯追求,千万不要为了炫技而把init当成了茴字的第N种写法到处去问。

record

record是一个非常有用的特性,它是不可变类型,其相等性是通过内部的几个属性来确定的,同时它支持我们以更加方便的方式、像定义值类型那样来定义不可变引用类型。
我们把之前的Product类改成record类型,如下所示:

public record Product
{public Product(int id, string productName, string description) => (Id, ProductName, Description) = (id, productName, description);public int Id { get; }public string ProductName { get; }public string Description { get; }
}

然后查看一下IL,可以看到record会被编译成类,同时继承了System.Object,并实现了IEquatable泛型接口。
编译器为我们提供的几个重要方法如下:

  • Equals

  • GetHashCode()

  • Clone

  • PrintMembers和ToString()

比较重要的三个方法

Equals

通过图片中的代码,我们知道比较两个record对象,首先需要比较类型是否相同,然后再依次比较内部属性。

GetHashCode()

record类型通过基类型以及所有的属性及字段的方式来计算HashCode,这在整个继承层次结构中增强了基于值的相等性,也就意味着两个同名同姓的人不会被认为是同一个人

Clone

这个方法貌似非常简单,实在看不出有什么特别的地方,那么我们通过后面的内容再来解释这个方法。

record在DDD值对象中的应用

record之前的定义方式

了解DDD值对象的小伙伴应该想到了,record类型的特性非常像DDD中关于值对象的描述,比如不可变性、其相等于是基于其内部的属性的等等,我们先来看下值类型的定义方式。

public abstract class ValueObject
{public static bool operator ==(ValueObject left, ValueObject right){if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)){return false;}return ReferenceEquals(left, null) || left.Equals(right);}public static bool operator !=(ValueObject left, ValueObject right){return !(left == right);}protected abstract IEnumerable<object> GetEqualityComponents();public override bool Equals(object obj){if (obj == null || obj.GetType() != GetType()){return false;}var other = (ValueObject)obj;return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());}public override int GetHashCode(){return GetEqualityComponents().Select(x => x != null ? x.GetHashCode() : 0).Aggregate((x, y) => x ^ y);}// Other utility methods
}
public class Address : ValueObject
{public string Street { get; private set; }public string City { get; private set; }public string State { get; private set; }public string Country { get; private set; }public string ZipCode { get; private set; }public Address(string street, string city, string state, string country, string zipcode){Street = street;City = city;State = state;Country = country;ZipCode = zipcode;}protected override IEnumerable<object> GetEqualityComponents(){// Using a yield return statement to return each element one at a timeyield return Street;yield return City;yield return State;yield return Country;yield return ZipCode;}public override string ToString(){return $"Street: {Street}, City: {City}, State: {State}, Country: {Country}, ZipCode: {ZipCode}";}
}

main方法如下:

static void Main(string[] args)
{Address address1 = new Address("aaa", "bbb", "ccc", "ddd", "fff");Console.WriteLine($"address1: {address1}");Address address2 = new Address("aaa", "bbb", "ccc", "ddd", "fff");Console.WriteLine($"address2: {address2}");Console.WriteLine($"address1 == address2: {address1 == address2}");string jsonAddress1 = address1.ToJson();Address jsonAddress1Deserialize = jsonAddress1.FromJson<Address>();Console.WriteLine($"jsonAddress1Deserialize == address1: {jsonAddress1Deserialize == address1}");Console.ReadKey();
}

运行结果如下:

基于class:
address1: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address2: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address1 == address2: True
jsonAddress1Deserialize == address1: True

采用record方式定义

如果有大量的值对象需要我们编写,这无疑是加重我们的开发量的,这个时候record就派上用场了,最简洁的record风格的代码如下所示,只有一行:

public record Address(string Street, string City, string State, string Country, string ZipCode);

IL代码如下图所示,从图中我们也可以看到record类型的对象,默认情况下用到了init来限制属性的只读特性。main方法代码不变,运行结果也没有因为Address从class变成record而发生改变

基于record:
address1: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address2: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address1 == address2: True
jsonAddress1Deserialize == address1: True

如此看来我们的代码节省的不止一点点,而是太多太多了,是不是很爽啊。

record对象属性值的更改

使用方式如下:

class Program
{static void Main(string[] args){Address address1 = new Address("aaa", "bbb", "ccc", "ddd", "fff");Console.WriteLine($"1. address1: {address1}");Address addressWith = address1 with { Street = "############" };Console.ReadKey();}
}public record Address(string Street, string City, string State, string Country, string ZipCode);

通过ILSpy查看如下所示:

private static void Main(string[] args)
{Address address1 = new Address("aaa", "bbb", "ccc", "ddd", "fff");Console.WriteLine($"1. address1: {address1}");Address address2 = address1.<Clone>$();address2.Street = "############";Address addressWith = address2;Console.ReadKey();
}

由此可以看到record在更改的时候,实际上是通过调用Clone而产生了浅拷贝的对象,这也非常符合DDD ValueObject的设计理念。

参考:

  • https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects

  • https://deviq.com/value-object/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306234.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 .NET Core 中的 EventCounters 衡量性能

背景对于每隔几毫秒发生的事件&#xff0c;最好使每个事件的开销较低&#xff08;小于一毫秒&#xff09;。 否则&#xff0c;对性能的影响将很大。 记录事件意味着你将向磁盘写入内容。 如果磁盘不够快&#xff0c;你将丢失事件。 你需要一个解决方案&#xff0c;而不是记录事…

基于 C# 的 ETL 大数据并行编程

作者&#xff1a;James Spinella译者&#xff1a;精致码农原文&#xff1a;https://bit.ly/3nGQu4J并行编程在历史上一直是软件开发中比较小众和复杂的环节&#xff0c;往往不值得头疼。但编写并行化应用只会越来越简单&#xff0c;一个应用同时利用设备 CPU 上的多个内核&…

一个小技巧助您减少if语句的状态判断

在进行项目的开发的过程中&#xff0c; if 语句是少不了的&#xff0c;但我们始终要有一颗消灭 if / else 语句的心。为了消灭if / else 我们引入了 短路器 的概念。短路器 有时候的确能精简我们的代码&#xff0c;但还不够&#xff0c;因此我参考了一个方法来继续消灭一部分 断…

抢先看:笔者亲历的2020年中国.NET开发者大会活动纪实

编者&#xff1a;2020年中国.NET开发者大会第一天活动已经结束&#xff0c;可以通过https://codechina.csdn.net/lives 会看。第二天的Workshop 也有直播哦。12020年12月19日的苏州工业园区&#xff0c;天公作美&#xff0c;阳光明媚&#xff0c;气象迷人&#xff0c;正是一个搞…

python魔术方法由谁定义_Python的魔术方法

魔术方法就是在定义的类中定义一些”不一般”的方法&#xff0c;使类的使用更方便、完善、健壮&#xff0c;是python特有的方法&#xff0c;一般都是前后包含两个下划线__的方法称为魔术方法&#xff0c;例如__new__。基本魔术方法有哪些__new__&#xff1a;是在一个对象实例化…

Swagger在header中添加token

概述平常做项目使用mvcwebapi&#xff0c;采取前后端分离的方式&#xff0c;后台提供API接口给前端开发人员。这个过程中遇到一个问题后台开发人员怎么提供接口说明文档给前端开发人员。为了解决这个问题&#xff0c;项目中引用swagger&#xff08;我比较喜欢戏称为“丝袜哥”&…

如何在 C# 中使用 数据注解

数据注解 是一种可以应用到 类 或者 类成员上用来指定类之间关系的一种 Attribute&#xff0c;它的应用场景比较多&#xff0c;可用来描述 UI 上如何进行数据展示&#xff0c;还可以用来做类属性的规则验证&#xff0c;这篇文章就来讨论为什么 注解 值得你去学习&#xff0c;以…

2020年中国.NET开发者大会第二天 WorkShop

工作坊1&#xff1a;使用 NCF 从 0 到 1 快速模块化开发/部署业务系统实战工作坊简介&#xff1a;本次工作坊由盛派开发团队亲自带领开发者使用 NCF&#xff08;NeuCharFramework&#xff09; 框架进行系统快速开发&#xff0c;进行现场实操训练&#xff0c;大家可以通过本次活…

mysql直接生成excel_MYSQL 将excel里面的数据直接生成sql语句

如何使用EXCEL生成SQL语句&#xff1f;将光标放到新的列上里面&#xff0c;然后在公式栏里面输入如下公式&#xff1a;"insert into t values("&A1&","&B1&","&C1&","&D1&")"效果图&#x…

OrchardCore实现模块化核心原理分析

【导读】ABP vNext并未过多探究&#xff0c;当然其基于DDD理念分层清晰&#xff0c;灵活性、扩展性自然也不在话下&#xff0c;但有些情况下我可能会首选OrchardCore&#xff0c;并非ABP vNext不可若改造项目&#xff0c;也因历史遗留问题&#xff0c;数据库表设计也可能存在不…

.NET 云原生架构师训练营(模块二 基础巩固 配置)--学习笔记

2.2.3 核心模块--配置IConfigurationOptionsASP.NET Core 中的配置&#xff1a;https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/?viewaspnetcore-5.0IConfigurationIConfiguration 的使用层级对象配置到 key-value 键值对转换通过环境变量修改日志…

.NET Conf 2020 - 基于ASP.NET Core构建可热插拔的插件化系统

文章标题&#xff1a;.NET Conf 2020 - 基于ASP.NET Core构建可热插拔的插件化系统作者&#xff1a;Lamond Lu项目地址&#xff1a;https://github.com/lamondlu/CoolCat博客&#xff1a;http://www.cnblogs.com/lwqlun以下是2020.12.19日的演讲文稿和视频&#xff1a;大家好&a…

多款主流编程语言,哪款开发软件最安全?

喜欢就关注我们吧&#xff01;在当下的市场环境中&#xff0c;除了掌握困扰软件的最常见安全问题外&#xff0c;开发人员还应该了解到底是什么问题在影响他们正在使用的编程语言。静态代码分析安全公司 Veracode 最近发布了一份年度软件安全状态&#xff08;SOSS&#xff09;报…

2020年终回顾:时间会回答成长,成长会回答梦想

前言2020年是脚踏实地&#xff0c;慢慢成长的一年&#xff0c;由于疫情的缘故&#xff0c;今年社区没有像去年一样举办多场线下活动&#xff0c;不过 .NET CONF CHINA 大会昨天也在苏州顺利召开&#xff0c;回顾这一年&#xff0c;也有不少惊喜与感悟2020年回顾公众号自从去年双…

java servlet 跳转_Servlet跳转方式sendReDirect()和forward()

在web应用服务中&#xff0c;经常会面对不同SERVLET之间的跳转&#xff0c;目前我们可以通过以下两种方式实现&#xff1a;1.RequestDispatcher.forward()2.ServletResponse.sendReDirect()两者的区别&#xff1a;1.redirect 方式可以跨应用访问,forward 只能在同一个应用中跳转…

明天面腾讯,我刷了这71道面试题...

激动人心的Conf 2020中国.NET开发者大会完美落幕&#xff0c;有幸去到现场&#xff0c;跟诸位.NET大佬、微软大咖、MVP面对面交流&#xff0c;内心很是鸡冻&#xff01;聊天中我注意到一个细节&#xff0c;很多公司的项目都在逐步用MySQL替换SQLServer&#xff0c;尤其是微服务…

java字符串切分_Java字符串分割(转)

java.lang.String的split()方法, JDK 1.4 or laterpublic String[] split(String regex,int limit)示例代码public class StringSplit {public static void main(String[] args) {String sourceStr "1,2,3,4,5";String[] sourceStrArray sourceStr.split(",&q…

面试官:. NET5源码里用到了哪些设计模式?懵!

作为微软最早迈向开源的重要软件之一&#xff0c;.NET 5的发布具有重要意义&#xff01;微软希望 .NET Framework 开发者能够迁移他们的代码和应用到 .NET 5.0 上&#xff0c;为明年发布的 .NET 6.0 将 Xamarin 开发者过渡到统一平台奠定基础。版本发布时间轴&#xff1a;.NET …

C# 中 ConcurrentDictionary 一定线程安全吗?

根据 .NET 官方文档的定义&#xff1a;ConcurrentDictionary<TKey,TValue> Class 表示可由多个线程同时访问的线程安全的键/值对集合。这也是我们在并发任务中比较常用的一个类型&#xff0c;但它真的是绝对线程安全的吗&#xff1f;仔细阅读官方文档&#xff0c;我们会发…

2020 .NET 开发者峰会顺利在苏州落幕,相关数据很喜人以及线上直播回看汇总

在2019年上海中国.NET开发者大会的基础上&#xff0c;2020年12月19-20日 继续以“开源、共享、创新” 为主题的第二届中国 .NET 开发者峰会&#xff08;.NET Conf China 2020&#xff09;在苏州人工智能智能产业创新中心落下帷幕&#xff0c;本次大会以线下城市苏州为中心&…