如何更改 C# Record 构造函数的行为

Record 是 C# 9 中的一个新功能。Record是从Structs借用的特殊类, 因为它们具有 基于值的相等性,您可以将它们视为两类类型之间的混合体。默认情况下,它们或多或少是不可变的,并且具有语法糖,使声明更容易和更简洁。但是,语法糖可能会掩盖更多标准任务,例如更改默认构造函数的行为。在某些情况下,您可能需要这样做以进行验证。本文将向您展示如何实现这一目标。

以这个简单的示例类为例:

public class StringValidator
{
public string InputString { get; } public StringValidator(string inputString)
{
if (string.IsNullOrEmpty(inputString)) throw new ArgumentNullException(nameof(inputString)); InputString = inputString;
}
}

很明显,如果消费者试图在没有有效字符串的情况下创建此类的实例,他们将收到异常。创建Record的标准语法如下所示:

    public record StringValidator(string InputString);

它既友好又简洁,但并不清楚您将如何验证字符串。此定义告诉编译器将有一个名为 的属性InputString,并且构造函数会将值从参数传递给该属性。我们需要删除语法糖来验证字符串。幸运的是,这很容易。我们不需要使用新语法来定义我们的Record。我们可以定义类似于类的Record,但将关键字类更改为Record。

public record StringValidator
{
public string InputString { get; } public StringValidator(string inputString)
{
if (string.IsNullOrEmpty(inputString)) throw new ArgumentNullException(nameof(inputString)); InputString = inputString;
}
}

不幸的是,这意味着我们不能使用 非破坏性突变。该with 关键字给我们创造了一些属性来更改Record的新版本的功能。这意味着我们不会修改Record的原始实例,但我们会得到它的副本。这是 Fluent API 和函数式编程的常用方法。这使我们能够保持不变性。

为了允许非破坏性突变,我们需要添加init 属性访问器。这与构造函数的工作方式类似,但仅在对象初始化期间调用。这是实现init 访问器的更完整的解决方案。这允许您共享构造函数逻辑和初始化逻辑。

using System;

namespace ConsoleApp25
{
class Program
{
static void Main(string[] args)
{
//This throws an exception from the constructor
//var stringValidator = new StringValidator(null); var stringValidator1 = new StringValidator("First");
var stringValidator2 = stringValidator1 with { InputString = "Second" };
Console.WriteLine(stringValidator2.InputString); //This throws an exception from the init accessor
//var stringValidator3 = stringValidator1 with { InputString = null }; //Output: Second
}
} public record StringValidator
{
private string inputString; public string InputString
{
get => inputString;
init
{
//This init accessor works like the set accessor
ValidateInputString(value);
inputString = value;
}
} public StringValidator(string inputString)
{
ValidateInputString(inputString);
InputString = inputString;
} public static void ValidateInputString(string inputString)
{
if (string.IsNullOrEmpty(inputString)) throw new ArgumentNullException(nameof(inputString));
}
}
}

Record构造函数应该有逻辑吗?

这是一个有争议的辩论,超出了本文的范围。很多人会争辩说你不应该把逻辑放在构造函数中。Record的设计鼓励您不要在构造函数或 init 访问器中放置逻辑。一般来说,Record应该及时代表数据的快照状态。您不需要应用逻辑,因为假设您知道此时数据的状态。但是,就像其他所有编程结构一样,无法知道Record可能会产生哪些用例。这是库 Urls 中的一个示例它将 URL 视为不可变Record:

using System.Net;

namespace Urls
{
public record QueryParameter
{
private string? fieldValue; public string FieldName { get; init; }
public string? Value
{
get => fieldValue; init
{
fieldValue = WebUtility.UrlDecode(value);
}
} public QueryParameter(string fieldName, string? value)
{
FieldName = fieldName;
fieldValue = WebUtility.UrlDecode(value);
} public override string ToString()
=> $"{FieldName}{(Value != null ? "=" : "")}{WebUtility.UrlEncode(Value)}";
}
}

我们确保在存储查询值时对其进行解码,然后在将其用作 Url 的一部分时对其进行编码。

你可能会问:为什么不把一切都Record下来?似乎会有与此相关的陷阱,但我们正在冒险进入新领域,我们尚未为 C# 上下文中的Record制定最佳实践。

总结

开发人员需要几年时间才能接受Record并制定使用它们的基本规则。您目前有一张白纸,您可以自由尝试,直到“专家”开始告诉您其他情况。我的建议是只使用Record来表示固定数据和最小逻辑。尽可能使用语法糖。但是,在某些情况下,构造函数中的最小验证可能是可行的。运用你的判断力,与你的团队讨论,权衡利弊。

如何更改 C# Record 构造函数的行为的更多相关文章

  1. JavaScript构造函数+原型创建对象,原型链+借用构造函数模式继承父类练习

    虽然经常说是做前端开发的,但常常使用的技术反而是JQuery比较多一点.在JavaScript的使用上相对而言少些.尤其是在创建对象使用原型链继承上面,在项目开发中很少用到.所以今天做个demo练习一 ...

  2. System.Web.Caching.Cache类 缓存 各种缓存依赖

    原文:System.Web.Caching.Cache类 缓存 各种缓存依赖 Cache类,是一个用于缓存常用信息的类.HttpRuntime.Cache以及HttpContext.Current.C ...

  3. System.Web.Caching.Cache类 缓存 各种缓存依赖(转)

    转自:http://www.cnblogs.com/kissdodog/archive/2013/05/07/3064895.html Cache类,是一个用于缓存常用信息的类.HttpRuntime ...

  4. System.Web.Caching.Cache类 Asp.Net缓存 各种缓存依赖

    Cache类,是一个用于缓存常用信息的类.HttpRuntime.Cache以及HttpContext.Current.Cache都是该类的实例. 一.属性 属性 说明 Count 获取存储在缓存中的 ...

  5. C# System.Web.Caching.Cache类 缓存 各种缓存依赖

    原文:https://www.cnblogs.com/kissdodog/archive/2013/05/07/3064895.html Cache类,是一个用于缓存常用信息的类.HttpRuntim ...

  6. 加快ASP。NET Core WEB API应用程序。第3部分

    下载source from GitHub 对ASP进行深度重构和优化.NET Core WEB API应用程序代码 介绍 第1部分.创建一个测试的RESTful WEB API应用程序. 第2部分.增 ...

  7. .Net中的AOP系列之构建一个汽车租赁应用

    返回<.Net中的AOP>系列学习总目录 本篇目录 开始一个新项目 没有AOP的生活 变更的代价 使用AOP重构 本系列的源码本人已托管于Coding上:点击查看. 本系列的实验环境:VS ...

  8. Angular2 从0到1 (二)

    第一节:Angular2 从0到1 (一)第三节:Angular2 从0到1 (三)第四节:Angular2 从0到1 (四) 作者:王芃 wpcfan@gmail.com 第二节:用Form表单做一 ...

  9. Java更新XML的四种常用方法简介

    本文简要的讨论了Java语言编程中更新XML文档的四种常用方法,并且分析这四种方法的优劣.其次,本文还对如何控制Java程序输出的XML文档的格式做了展开论述. JAXP是Java API for X ...

  10. 如何基于对话框的project基于改变BCG的

    一,stdafx.h 增加在下面的例子.BCGCBProInc.h间接介绍lib.   #include <BCGCBProInc.h> // BCGControlBar Pro #if ...

随机推荐

  1. python · matplotlib | 如何绘制子图

    代码: import matplotlib.pyplot as plt import matplotlib matplotlib.rc("font",family='MicroSo ...

  2. Makefile中的+=、:=、?=

    1.= "="是最普通的等号,然而在Makefile中确实最容易搞错的赋值等号,使用"="进行赋值,变量的值是整个makefile中最后被指定的值.不太容易理解 ...

  3. [转帖]Kubernetes1.25.6部署文档 使用cri-docker部署K8s1.25.6

    https://zhuanlan.zhihu.com/p/600808149 本文档将通过kubeadm+docker部署K8s集群,本次集群使用的容器运行工具为docker,K8s的容器运行工具也可 ...

  4. 快速迁移Grafana/Prometheus等的方式方法

    快速迁移Grafana/Prometheus等的方式方法 背景 有一套鲲鹏环境下面的Grafana监控平台. 同事想能够将平台内的时序数据库等迁移到一个别的机器上进行使用. 自从自己开始搞国产化之后, ...

  5. 使用Grafana监控Nacos的简单过程

    使用Grafana监控Nacos的简单过程 背景 与kafka一样,想同期监控一下nacos. 发现nacos跟minio等一样都有对应的 metrics的暴露接口. 所以这边简单使用一下. stud ...

  6. [转帖]Jmeter连接InfluxDB2.0.4

    Jmeter连接InfluxDB2.0.4 问题描述:在用Jmeter+InfluxDB构建监控时,因为docker构建的InfluxDB的版本是2.0.4,按照网上的教程进行后端监听器的填写,但是一 ...

  7. [转帖]LVS入门篇(五)之LVS+Keepalived实战

    LVS入门篇(五)之LVS+Keepalived实战 https://www.cnblogs.com/linuxk/p/9365189.html 一.实验架构和环境说明 (1)本次基于VMware W ...

  8. [转帖]Sar的一次使用案例

    https://www.jianshu.com/p/b93342d43e13 问题现象 有一台机器,在某个时间点OS类似无响应,造成使用者感觉在该时间点机器应该发生重启,就此问题进行分析. 日志查看 ...

  9. [转帖]NGINX 局限太多,Cloudflare 最终放弃它并用 Rust 自研了全新替代品

    https://www.infoq.cn/news/s2fa603MsEENsCmibTYI 长期以来,NGINX 可以说是网站安全和托管服务提供商 Cloudflare 的核心,是其所使用的基础软件 ...

  10. [转帖]银河麒麟服务器操作系统V10SP1-x86_64系统环境下部署aarch64虚拟机

    文章目录 主机系统环境 搭建aarch64虚拟机环境 ①安装"虚拟系统管理器" ②编译安装Qemu for Aarch64 ③获取aarch64架构的qcow2镜像 ④使用qcow ...