在 C# 9 记录上使用“with”时忽略特定字段?

如何解决在 C# 9 记录上使用“with”时忽略特定字段?

在使用 record 关键字创建 C# 9 with 的新实例时,我想忽略某些字段,而不是将它们也复制到新实例中。

在以下示例中,我有一个 Hash 属性。因为它在计算上非常昂贵,所以只在需要时才计算然后缓存(我有一个深度不可变的记录,所以对于一个实例来说哈希永远不会改变)。

public record MyRecord {

   // All truely immutable properties
   public int ThisAndManyMoreComplicatedProperties { get; init; } 
   // ...

   // Compute only when required,but then cache it
   public string Hash {
      get {
         if (hash == null)
            hash = ComputeHash();
         return hash;
      }
   }

   private string? hash = null;
}

打电话时

MyRecord myRecord = ...;
var changedRecord = myRecord with { AnyProp = ... };

changedRecord 包含来自 hashmyRecord 值,但我想要的是再次使用默认值 null

是否有机会将 hash 字段标记为“transient”/“internal”/“reallyprivate”...,或者我是否必须编写自己的复制构造函数来模仿此功能?

解决方法

我找到了解决我的问题的方法。这不会解决一般问题,而且还有另一个缺点:我必须缓存对象的最后状态,直到重新计算散列。我知道这是潜在的繁重计算和更高的内存使用量之间的权衡。

诀窍是在计算散列时记住最后一个对象引用。再次调用 Hash 属性时,我检查对象引用是否已同时更改(即是否创建了新对象)。

public string Hash {
   get {
      if (hash == null || false == ReferenceEquals(this,hashRef)) {
         hash = ComputeHash();
         hashRef = this;
      }
      return hash;
   }
}
private string? hash = null;
private MyRecord? hashRef = null;

我仍在寻找更好的解决方案。

编辑:我推荐Heinzi's solution

,

我找到了一种解决方法:您可以(ab)使用继承将复制构造函数拆分为两部分:仅用于 hash 的手动部分(在基类中)和自动生成的部分在派生类中复制所有有价值的数据字段。

这还有一个额外的好处,就是抽象掉你的散列(非)缓存逻辑。这是一个最小的示例 (fiddle):

abstract record HashableRecord
{
    protected string hash;
    protected abstract string CalculateHash();
    
    public string Hash 
    {
        get
        {
            if (hash == null)
            {
                hash = CalculateHash(); // do expensive stuff here
                Console.WriteLine($"Calculating hash {hash}");
            }
            return hash;
        }
    }
    
    // Empty copy constructor,because we explicitly *don't* want
    // to copy hash.
    public HashableRecord(HashableRecord other) { }
}

record Data : HashableRecord
{
    public string Value1 { get; init; }
    public string Value2 { get; init; }

    protected override string CalculateHash() 
        => hash = Value1 + Value2; // do expensive stuff here
}

public static void Main()
{
    var a = new Data { Value1 = "A",Value2 = "A" };
    
    // outputs:
    // Calculating hash AA
    // AA
    Console.WriteLine(a.Hash);

    var b = a with { Value2 = "B" };
    
    // outputs:
    // AA
    // Calculating hash AB
    // AB
    Console.WriteLine(a.Hash);
    Console.WriteLine(b.Hash);
}
,

正如你看到的 sharplab.io 反编译,with 调用被翻译成 <Clone>$() 方法调用,它内部调用编译器生成的复制构造函数,所以你需要定义自己的 {{3 }} 以防止调用 Hash

同样如 with 关键字 copy constructor 所述:

如果您需要自定义记录复制语义,请显式声明具有所需行为的复制构造函数。

,

我认为唯一允许这样做的内置机制是“复制构造函数”。如 this post 中所述:

一条记录隐含地定义了一个受保护的“复制构造函数”——一个构造函数,它接受一个现有的记录对象,并将它逐个字段地复制到新的......

“复制构造函数”只是一个构造函数,它接收一个相同类型的记录实例作为参数。如果您只是实现此构造函数,则可以覆盖 with 表达式的默认行为。我已经根据您的代码进行了测试,这是记录声明:

public record MyRecord
{
    protected MyRecord(MyRecord original)
    {
        ThisAndMayMoreComplicatedProperties = original.ThisAndMayMoreComplicatedProperties;
        hash = null;
    }

    public int ThisAndMayMoreComplicatedProperties { get; init; }

    string? hash = null;
    public string Hash
    {
        get
        {
            if (hash is null)
            {
                Console.WriteLine("The stored hash is currently null.");
            }
            return hash ??= ComputeHash();
        }
    }

    string ComputeHash() => "".PadLeft(100,'A');
}

请注意,当我调用属性 getter 时,我会检查 hash 是否为空并打印一条消息。然后我做了一个小程序来检查:

var record = new MyRecord { ThisAndMayMoreComplicatedProperties = 100 };
Console.WriteLine($"{record.Hash}");

var newRecord = record with { ThisAndMayMoreComplicatedProperties = 200 };
Console.WriteLine($"{newRecord.Hash}");

如果您运行此程序,您会注意到对 Hash 的两次调用都将打印私有 hash 为空的消息。如果您注释复制构造函数,您将看到只有第一次调用打印空值。

所以我认为这可以解决您的问题。这种方法的缺点是您必须手动复制记录的每个属性,这可能非常烦人。如果你记录有很多属性,你可以使用反射来迭代然后只复制你想要的。您还可以定义自定义 Attribute 来标记忽略字段。但请记住,使用反射总是有处理开销。

,

如果我对您的理解正确,您想使用现有 MyRecord 对象的某些属性创建一个新的 MyRecord 对象?

我认为应该按照以下方式行事:

MyRecord myRecord = ...;
var changedRecord = new MyRecord with { AnyProp = myRecord.AnyProp... };

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res