如何更改 C# Record 构造函数的行为
如何更改 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来表示固定数据和最小逻辑。尽可能使用语法糖。但是,在某些情况下,构造函数中的最小验证可能是可行的。运用你的判断力,与你的团队讨论,权衡利弊。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2019-09-21 TDD的简单实践