如何在C# Record中实现依赖其他属性的自动属性
问题描述
现有以下代码:
class AA(string a, string b, string? c) { ... } record BB { public string A { get; init; }; public string B { get; init; }; public string? C { get; init; }; public string? D { get; init; }; }
我想给BB添加一个AA类型的属性,期望实现如下效果:
record BB { public string A { get; init; }; public string B { get; init; }; public string? C { get; init; }; public string? D { get; init; }; public AA Aa { get; } = new AA(A, B, C); }
但这段代码无法编译,推测原因是初始化Aa时BB的成员还未完成初始化。
于是我尝试了下面的写法,仍然无法运行:
record BB { public string A { get; init; }; public string B { get; init; }; public string? C { get; init; }; public string? D { get; init; }; private Lazy<AA> _Aa = new Lazy(new AA(A, B, C)); public AA Aa => _Aa.Value; }
我原本以为这种写法可行,因为Lazy会在调用Lazy.Value时才执行new AA(A, B, C),而Aa和Lazy.Value会在BB对象初始化完成后才被调用。但即便new AA(...)执行时A、B、C已经初始化,编译器还是不允许这么写。
我希望在不编写复杂代码(如下方示例,仅作演示,不保证ReaderWriterLockSlim用法正确)的前提下实现需求:
record BB { public string A { get; init; }; public string B { get; init; }; public string? C { get; init; }; public string? D { get; init; }; private AA? _Aa; private readonly ReaderWriterLockSlim _AaLock = new(); public AA Aa { get { try { _AaLock.EnterReadLock(); if (_Aa == null) { try { _AaLock.EnterUpgradeableReadLock(); if (_Aa == null) { try { _AaLock.EnterWriteLock(); _Aa = new(A, B, C); } finally { _AaLock.ExitWriteLock(); } } } finally { _AaLock.ExitUpgradeableReadLock(); } } } finally { _AaLock.ExitReadLock(); } } } }
我知道如果放弃init初始化方式,改用显式构造函数可以实现,但想了解是否有支持init对象创建方式的解决方案,即支持:
var bb = new BB() { A = "my A", B = "my B", C = "my C", }
而非仅支持以下写法:
var bb = new BB("my A", "my B", "my C", null); // BB的构造函数如下: public BB(string a, string b, string? c, string? d) { A = a; B = b; C = c; D = d; Aa = new AA(a, b, c); }
可行方案(已验证)
我已确认Ivan Petrov的方案可行,以下是测试代码:
AA类对应代码(SomeRecordKey)
namespace ZZZ_CodeTest.Records { public record SomeRecordKey(string Field1, string Field2, string? Field3) { public string Field1 { get; init; } = Field1; public string Field2 { get; init; } = Field2; public string? Field3 { get; init; } = Field3; } }
BB类对应代码(SomeRecord)
namespace ZZZ_CodeTest.Records { public record SomeRecord { protected SomeRecord(SomeRecord original) { _key = null; this.Field1 = original.Field1; // 缺失这四行时测试不通过 this.Field2 = original.Field2; this.Field3 = original.Field3; this.Field4 = original.Field4; } private SomeRecordKey? _key; public SomeRecordKey Key { get { // 可查看文档了解其他重载(包括同步对象) LazyInitializer.EnsureInitialized(ref _key, () => new SomeRecordKey(Field1, Field2, Field3)); return _key; } } public required string Field1 { get; init; } public required string Field2 { get; init; } public string? Field3 { get; init; } public string? Field4 { get; init; } } }
MSTest测试类
namespace ZZZ_CodeTest.Records { [TestClass] public sealed class SomeRecordTest { [TestMethod] [DataRow("A", "B", null, null)] [DataRow("A", "B", "C", null)] [DataRow("A", "B", null, "D")] [DataRow("A", "B", "C", "D")] public void TestEquals(string field1, string field2, string? field3, string? field4) { SomeRecord sr1 = new() { Field1 = field1, Field2 = field2, Field3 = field3, Field4 = field4, }; SomeRecord sr2 = new() { Field1 = field1, Field2 = field2, Field3 = field3, Field4 = field4, }; Assert.AreEqual(sr1, sr2); } [TestMethod] [DataRow("A", "B", null, null, "E")] [DataRow("A", "B", "C", null, "E")] [DataRow("A", "B", null, "D", "E")] [DataRow("A", "B", "C", "D", "E")] public void TestWith(string field1, string field2, string? field3, string? field4, string newField1) { SomeRecord sr = new() { Field1 = field1, Field2 = field2, Field3 = field3, Field4 = field4, }; Assert.AreEqual(sr.Field1, sr.Key.Field1); Assert.AreEqual(sr.Field2, sr.Key.Field2); Assert.AreEqual(sr.Field3, sr.Key.Field3); SomeRecord mr = sr with { Field1 = newField1, }; Assert.AreNotEqual(sr, mr); Assert.AreNotEqual(sr.Key, mr.Key); Assert.AreNotEqual(sr.Field1, mr.Field1); Assert.AreEqual(sr.Field2, mr.Field2); Assert.AreEqual(sr.Field3, mr.Field3); Assert.AreEqual(sr.Field4, mr.Field4); Assert.AreEqual(mr.Field1, newField1); Assert.AreEqual(mr.Field1, mr.Key.Field1); Assert.AreEqual(mr.Field2, mr.Key.Field2); Assert.AreEqual(mr.Field3, mr.Key.Field3); } } }
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

