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

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

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

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

不幸的是,这意味着我们不能使用 非破坏性突变[3]。该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 accessorValidateInputString(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 中的[4]一个示例[5], [6]它将 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来表示固定数据和最小逻辑。尽可能使用语法糖。但是,在某些情况下,构造函数中的最小验证可能是可行的。运用你的判断力,与你的团队讨论,权衡利弊。

References

[1] Record: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records
[2] Structs: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct
[3] 非破坏性突变: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#non-destructive-mutation
[4] Urls 中的: https://github.com/MelbourneDeveloper/Urls
[5] 示例: https://github.com/MelbourneDeveloper/Urls/blob/5f55a9437cfac1223711d616bfdbeb72b230d263/src/Uris/QueryParameter.cs#L5
[6] , : https://github.com/MelbourneDeveloper/Urls

如何更改 C# Record 构造函数的行为相关推荐

  1. java.lang.Record:规范草案

    建议的Java记录的工作仍在继续. Brian Goetz 昨天在OpenJDK amber-spec-experts邮件列表上启动了三个新线程,其中两个专注于Java Records. 这两个面向记 ...

  2. 更改窗口图标并将其显示在任务栏

    以下两个函数可以为应用程序中的各子窗口显示一个任务条到任务栏并更改它们的图标.对那些象QQ一样隐藏主窗口的应用程序特别有用. //函数用途:更改一个窗口的图标并将其显示在任务栏.任务切换条.任务管理器 ...

  3. 无涯教程- Java 14 – Record类型介绍

    Java中Record类型是Java 14中的预览函数引入的,并且应作为普通的 不可变 数据类,用于在类和应用程序之间进行数据传输. 像Enum 一样,Record也是一个特殊的类输入Java.它旨在 ...

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

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

  5. 加速ASP.NET Core WEB API应用程序。 第三部分

    深度重构和完善ASP.NET Core WEB API应用程序代码 (Deep refactoring and refinement of ASP.NET Core WEB API applicati ...

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

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

  7. 如何才能信任你的深度学习代码?

    深度学习是一门很难评估代码正确性的学科.随机初始化.庞大的数据集和权重的有限可解释性意味着,要找到模型为什么不能训练的确切问题,大多数时候都需要反复试验.在传统的软件开发中,自动化单元测试是确定代码是 ...

  8. JavaScript对象中的构造方法

    JavaScript类/对象可以具有构造函数吗? 它们是如何创建的? #1楼 这是一个构造函数: function MyClass() {} 当你做 var myObj = new MyClass() ...

  9. 总结C#中窗体间传递数据的几种方法

    在编写C#windows应用程序的时候我们经常会遇到这种问题,怎么样在两个窗体间传递数据呢?以下是我整理的网上的各种方法,在遇到一个实际问题:在form1中打开一个form2窗口作为录入界面,将录入的 ...

最新文章

  1. PAT(甲级)2019年冬季考试 7-2 Block Reversing
  2. 关于c++深拷贝与浅拷贝
  3. 《Business Rules Engine Overview》--《业务规则引擎概述》write by Mark Kamoski
  4. 使用Delphi编写棋牌类游戏 – 设计篇(3)
  5. myeclipse中hibernate出错
  6. html5人脸登录,基于HTML5 的人脸识别活体认证
  7. iOS AVPlayer 使用总结
  8. 朗读评价语言集锦_教师-课堂评价语言集锦.doc
  9. 民生银行计算机研发笔试题,民生银行提前批 “民芯计划” 技术岗笔试算法题...
  10. 《统计学习方法(第二版)》学习笔记 第五章 决策树
  11. MapGIS转换为ArcGIS小结
  12. 程序员离职的3个大忌!
  13. 国行白色 ipad 4 16G
  14. padding有两种设置方式:
  15. 王者归来!中国软件生态大会再次登场 16城市生态合作大幕拉开
  16. 数据可视化-豆瓣影评数据分析(FineBI)
  17. 神经网络——PRML
  18. Python+Streamlit aggrid+MongoDB GridFS构建低代码文档管理应用(文档查询下载实用篇)
  19. windows 2000 堆管理的一点心得和体会
  20. 除和除以以及被除的区别

热门文章

  1. [导入]C++程序随笔
  2. context:annotation-config vs context:component-scan
  3. AJAX+json+jquery实现预加载瀑布流布局
  4. 为什么一点onclick按钮就提交表单?
  5. UVa 11346 - Probability
  6. 带有控制按钮的图片滚动
  7. css两栏式布局示例
  8. 如何种植屡获殊荣的青豆
  9. 大厂高级前端面试题答案
  10. 虚拟DOM Diff算法解析