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

如何更改 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来表示固定数据和最小逻辑。尽可能使用语法糖。但是,在某些情况下,构造函数中的最小验证可能是可行的。运用你的判断力,与你的团队讨论,权衡利弊。

到此这篇关于C# Record构造函数行为更改的文章就介绍到这了,更多相关C# 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

(0)

相关推荐

  • 详解C#编程中构造函数的使用

    当类或结构创建时,其构造函数调用.构造函数与选件类或结构相同,并且,它们通常用于初始化新对象的数据成员. 在下面的示例中,使用一个简单的构造函数定义了名为 Taxi 的类.然后使用 new 运算符来实例化该类.在为新对象分配内存之后,new 运算符立即调用 Taxi 构造函数. public class Taxi { public bool isInitialized; public Taxi() { isInitialized = true; } } class TestTaxi { stat

  • C# 9 新特性——record的相关总结

    Intro C# 9 中引入了 record,record 是一个特殊类,用它来实现 model 在有些情况下会非常的好用 Sample record RecordPerson { public string Name { get; init; } public int Age { get; init; } } record RecordPerson2(string Name, int Age); public static void MainTest() { var p1 = new Reco

  • C#私有构造函数使用示例

    声明空构造函数可阻止自动生成默认构造函数.注意,如果您不对构造函数使用访问修饰符,则在默认情况下它仍为私有构造函数.但是,通常显式地使用 private 修饰符来清楚地表明该类不能被实例化. 示例代码: 复制代码 代码如下: public class PrivateConClass{private static PrivateConClass pcc; private PrivateConClass(){Console.WriteLine("This private constructure f

  • c# record的使用场景

    Intro 之前我们有介绍过 record 基本知识,record 会实现基于值的类型比较,最近遇到的几个问题觉得用 record 来解决会非常方便,分享一下 基于值的类型比较 最近有遇到一个场景,需要比较两个 JSON 字符串是否相等,字符串比较简单,就是一个固定值的 Dictionary,或者认为它就是一个简单的 Model,但是 JSON 字符串的的属性顺序可能不同,比如说下面的这个示例: {"Id":1, "Name":"Tom"}, {

  • C# 静态构造函数使用总结

    •静态构造函数既没有访问修饰符,也没有参数. •如果没有编写静态构造函数,而这时类中包含带有初始值设定的静态字段,那么编译器会自动生成默认的静态构造函数. •在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类,也就是说我们无法直接调用静态构造函数,也就无法控制什么时候执行静态构造函数了. •如果类中包含用来开始执行的 Main 方法,则该类的静态构造函数将在调用 Main 方法之前执行. •如果类中的静态字段带有初始化,则静态字段的初始化语句将在静态构造函数之前运

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

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

  • C++聚合关系类的构造函数的调用顺序详解

    如图,表示一个聚合关系 下面就用简单的代码来实现 #pragma once class Engine { public: Engine(); ~Engine(); }; Engine.h #include <iostream> #include "Engine.h" using namespace std; Engine::Engine() { cout << "调用构造函数:Engine()" << endl; } Engine

  • 关于java中构造函数的一些知识详解

    java的构造函数是一个非常重要的作用,首先java里的构造函数是可以重载的,而且因为也是可以继承在父类的构造函数,所以在子类里,首先必然是调用父类的构造函数.可以看下面的两个例子来对比: public class Test { public static void main(String args[]) { B b = new B(100); } } class A { public A() { System.out.println("A without any parameter"

  • C++中构造函数与析构函数的详解及其作用介绍

    目录 构造函数 默认构造函数 有参构造函数 析构函数 析构函数例子 析构函数执行时机 局部对象 全局对象 构造函数 构造函数 (constructor) 是一种特殊的成员函数. 它会在每次创建类的新对象时执行. 构造函数的名称与类的名称是完全相同的, 并且不会返回任何类型. 构造函数可用于为某些成员变量设置初始值. 格式: Class::Class(); // 构造函数 默认构造函数 如果用户自己没有定义构造函数, C++ 系统会自动生成一个默认构造函数. 这个构造函数体是空的, 没有参数, 不

  • C++右值引用与移动构造函数基础与应用详解

    目录 1.右值引用 1.1左值右值的纯右值将亡值右值 1.2右值引用和左值引用 2.移动构造函数 2.1完美的移动转发 1.右值引用 右值引用是 C++11 引入的与 Lambda 表达式齐名的重要特性之一.它的引入解决了 C++ 中大量的历史遗留问题, 消除了诸如 std::vector.std::string 之类的额外开销, 也才使得函数对象容器 std::function 成为了可能. 1.1左值右值的纯右值将亡值右值 要弄明白右值引用到底是怎么一回事,必须要对左值和右值做一个明确的理解

  • JavaScript对象字面量和构造函数原理与用法详解

    本文实例讲述了JavaScript对象字面量和构造函数.分享给大家供大家参考,具体如下: 对象中只有两种属性:(一种比较细的分法) 属性(数据属性)比如:名字,年龄,性别,出版社,地址等信息: 方法(封装代码的属性:函数 ,在这也是一种属性). 在JS中对象的字面量和构造函数是非常的重点,其实在其他的语言中,是没有对象字面量的. 一.对象的字面量的语法: { 属性名: 属性值, 属性名: 属性值, 方法名: 你们函数 } 这个大括号括起来的整个代码块就是叫做对象. var p1 = { } ①访

  • C++拷贝构造函数(深拷贝与浅拷贝)详解

    对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. 复制代码 代码如下: #include <iostream>using namespace std; class CExample {private:    int a;public:    CExample(int b)    { a=b;}    void Show ()    {       

  • c/c++拷贝构造函数和关键字explicit详解

    关键字explicit 修饰构造方法的关键字,加上了,就告诉编译器,不可以隐式初始化对象:不加就可以隐式初始化对象: 下面的代码是可以正常编译执行的,但是加了关键字explicit,编译就会错我,因为Test t = 100;是隐式初始化对象,但是如果加上强制类型转换后,就不会有错误了. 强制类型转换:Test t = (Test)100; class Test{ public: Test(int d):data(d){//explicit cout << "C:" <

  • C++构造函数的初始化列表详解

    目录 1.问题 2.解决方法(初始化列表) 3.顺序问题 总结 1.问题 class A { private: int m_a; public: A(int a) { cout << "A(int a)......." << endl; m_a = a; } void print() { cout <<"m_a=" << m_a << endl; } }; class B { private: int m_

  • Java构造函数与普通函数用法详解

    函数也被称为方法! 函数的作用及特点: 1.用于定义功能,将功能封装. 2.可以提高代码的复用性. 函数注意事项: 1.不能进行函数套用(不可以在函数内定义函数). 2.函数只有被调用才能被执行. 3.基本数据类型(String.int.-.)修饰的函数类型,要有return返回值. 4.void修饰的函数,函数中的return语句可以省略不写. 5.函数名可以根据需求进行命名. 代码示例:(有无函数/方法的区别) 无函数/方法代码例子: public class NoFunc { public

随机推荐