使用“时忽略特定字段”;加上;在C#9记录上?
使用使用“时忽略特定字段”;加上;在C#9记录上?,c#,c#-9.0,c#-record-type,C#,C# 9.0,C# Record Type,使用with关键字创建C#9记录的新实例时,我希望忽略一些字段,而不是将它们复制到新实例中 在下面的示例中,我有一个Hash属性。因为它在计算上非常昂贵,所以它只在需要时计算,然后缓存(我有一个非常不可变的记录,所以哈希值对于一个实例永远不会改变) 公共记录MyRecord{ //所有真正不变的属性 public int thisandmanymorecomplicated属性{get;init;} // ... //仅在需要时计算,然后缓存它 公共字符串散列{ 得到{ if(hash==nul
with
关键字创建C#9记录的新实例时,我希望忽略一些字段,而不是将它们复制到新实例中
在下面的示例中,我有一个Hash
属性。因为它在计算上非常昂贵,所以它只在需要时计算,然后缓存(我有一个非常不可变的记录,所以哈希值对于一个实例永远不会改变)
公共记录MyRecord{
//所有真正不变的属性
public int thisandmanymorecomplicated属性{get;init;}
// ...
//仅在需要时计算,然后缓存它
公共字符串散列{
得到{
if(hash==null)
hash=ComputeHash();
返回散列;
}
}
私有字符串?散列=null;
}
打电话的时候
MyRecord MyRecord=。。。;
var changedRecord=myRecord,带有{AnyProp=…};
changedRecord
包含myRecord
中的hash
值,但我想要的是默认值null
是否有机会将散列
字段标记为“transient”/“internal”/“reallyprivate”…,或者我必须编写自己的复制构造函数来模拟此功能?如果我正确理解您的意思,您希望创建一个具有现有MyRecord对象某些属性的新MyRecord对象
我认为应该采取以下措施:
MyRecord myRecord = ...;
var changedRecord = new MyRecord with { AnyProp = myRecord.AnyProp... };
正如您在反编译中所看到的那样,with
调用被转换为$()
方法调用,该方法调用在内部调用编译器生成的复制构造函数,因此您需要定义自己的方法来防止调用散列
同样,带有
关键字的说明:
如果需要自定义记录复制语义,请显式声明具有所需行为的复制构造函数
我认为唯一允许这样做的内置机制是“复制构造函数”。如以下文件所述:
记录隐式定义了一个受保护的“复制构造函数”—一个构造函数,它接受现有的记录对象,并将其逐字段复制到新的记录对象
“复制构造函数”只是一个构造函数,它接收与参数类型相同的记录实例。如果只实现此构造函数,则可以使用
表达式覆盖的默认行为。我已经根据您的代码进行了测试,下面是记录声明:
公共记录MyRecord
{
受保护的MyRecord(MyRecord原件)
{
this和maymorecomplementdproperties=原始。this和maymorecomplementdproperties;
hash=null;
}
public int this和maymore使属性{get;init;}
string?hash=null;
公共字符串散列
{
得到
{
if(散列为空)
{
WriteLine(“存储的哈希当前为空。”);
}
返回hash???=ComputeHash();
}
}
字符串ComputeHash()=>“”.PadLeft(100,'A');
}
请注意,当我调用属性getter时,我会检查哈希值是否为null并打印一条消息。然后我做了一个小程序来检查:
var-record=new-MyRecord{thisandmaydromedproperties=100};
Console.WriteLine($“{record.Hash}”);
var newRecord=具有{thisandmaymoredproperties=200}的记录;
Console.WriteLine($“{newRecord.Hash}”);
如果运行此命令,您会注意到对Hash
的两个调用都将打印私有Hash
为空的消息。如果对复制构造函数进行注释,您将看到只有第一个调用打印空值
所以我认为这解决了你的问题。这种方法的缺点是,您必须手动复制记录的每个属性,这可能非常烦人。如果您的记录有很多属性,您可以使用反射来迭代,然后只复制您想要的属性。您还可以定义自定义属性
,以标记忽略字段。但请记住,使用反射总是有处理开销。我找到了解决问题的方法。这并不能解决一般问题,它还有另一个缺点:我必须缓存对象的最后一个状态,直到重新计算哈希为止。我知道这是一个潜在的繁重的计算和更高的内存使用之间的折衷
诀窍是在计算哈希时记住最后一个对象引用。再次调用Hash
属性时,我会检查对象引用是否已更改(即是否创建了新对象)
公共字符串哈希{
得到{
if(hash==null | | false==ReferenceEquals(this,hashRef)){
hash=ComputeHash();
hashRef=这个;
}
返回散列;
}
}
私人字符串?hash=null;
私人档案?hashRef=null;
我仍在寻找更好的解决方案
编辑:我推荐 我找到了一个解决方法:您可以(ab)使用继承将复制构造函数分为两部分:一部分是仅用于哈希的手动构造函数(在基类中),另一部分是在派生类中自动生成的构造函数,用于复制所有有价值的数据字段
这还有一个额外的优点,即抽象掉散列(非)缓存逻辑。下面是一个最小的示例():
是的,我非常确定没有内置的解决方案,这是这个用例的预期解决方案。我有点不太理解你的要求。。当使用
克隆记录时,新记录将获得现有哈希值(它是一个克隆,因此它应该是相同的哈希值),而不是再次运行哈希值,而只是通过值的副本(因此它很便宜),但肯定是AnyProp的setter(因为它正在更改记录,因此会更改哈希值)将再次将散列设置为null,以便在下次请求时计算它(分解AnyProp的新值)和cop
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);
}