C# Record构造函数的行为更改详解

作者:Christian Findla 时间:2022-01-21 04:50:11 

如何更改 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 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 中的[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

来源:https://mp.weixin.qq.com/s/ogwuo7T6tU5fBJC5l0OUUA

标签:c#,record,构造函数
0
投稿

猜你喜欢

  • java 中堆内存和栈内存理解

    2023-04-23 19:24:05
  • C#实现DataTable映射成Model的方法(附源码)

    2023-03-12 06:10:56
  • win7中C#的winForm编程使用savefiledialog不能弹出保存窗体的解决方法

    2022-09-18 07:27:57
  • SpringMVC中事务是否可以加在Controller层的问题

    2021-12-09 01:20:15
  • 利用Distinct()内置方法对List集合的去重问题详解

    2023-01-31 00:45:30
  • Spring-Data-JPA整合MySQL和配置的方法

    2023-10-29 10:19:41
  • myBatis实现三 级嵌套复杂对象的赋值问题

    2023-11-23 06:42:26
  • Java设计模式之命令模式

    2022-06-17 22:49:07
  • Android+SQLite数据库实现的生词记事本功能实例

    2023-06-18 10:41:35
  • 经典实例讲解C#递归算法

    2022-04-11 22:34:16
  • idea2020.1无法自动加载maven依赖的jar包问题及解决方法

    2021-06-13 17:40:36
  • C#使用CefSharp实现内嵌网页详解

    2022-04-12 15:02:00
  • SpringCloud Nacos + Ribbon 调用服务的实现方式(两种)

    2023-11-18 09:30:38
  • Java进程cpu占用过高问题解决

    2021-08-09 00:16:59
  • 基于springboot 配置文件context-path的坑

    2021-07-04 17:37:27
  • C#实现rar压缩与解压缩文件的方法

    2021-11-04 20:33:28
  • IDEA设置背景为自定义照片的操作方法

    2022-12-28 09:13:08
  • 使用C#获取远程图片 Form用户名与密码Authorization认证的实现

    2022-01-22 21:44:06
  • C#仿Windows XP自带的扫雷游戏

    2023-07-30 07:40:48
  • 浅析java移位符的具体使用

    2023-12-21 09:36:13
  • asp之家 软件编程 m.aspxhome.com