保留严格访问修饰符同时实现类序列化的方法
解决方案:保持基类封装性的同时实现反序列化初始化
要解决这个问题,核心思路是让基类自己负责私有字段的初始化逻辑,同时通过受保护的方法给派生类提供合法的初始化入口,完全避免弱化基类成员的访问权限。以下是具体实现步骤和代码示例:
1. 调整基类设计,坚守封装原则
首先修改基类,添加一个供反序列化使用的受保护无参数构造函数,并提供一个受保护的初始化方法来设置私有列表字段。为了保证构造后字段不可修改,我们用只读属性对外暴露列表的只读视图:
[DataContract] internal abstract class MyBase { // 去掉readonly(因为需要在反序列化回调中初始化),但通过只读属性保证外部不可修改 private List<int> myIntList; // 对外暴露只读视图,确保构造后列表内容不会被修改 internal IReadOnlyList<int> MyIntList => myIntList.AsReadOnly(); // 正常业务逻辑的构造函数 internal MyBase(List<int> anIntList) { myIntList = anIntList ?? throw new ArgumentNullException(nameof(anIntList)); } // 供反序列化使用的受保护无参数构造函数(仅派生类可调用) protected MyBase() { } // 受保护的初始化方法,仅允许在字段未初始化时设置值 protected void InitializeMyIntList(List<int> list) { if (myIntList == null) { myIntList = list ?? throw new ArgumentNullException(nameof(list)); } } }
2. 修改派生类,实现反序列化回调
派生类需要添加一个私有无参数构造函数(供DataContractSerializer调用),并在反序列化完成后调用基类的初始化方法,传入自身的静态只读列表:
[DataContract] internal sealed class MyDerived : MyBase { private static readonly List<int> derivedIntList = new List<int> { 1, 2, 3 }; // 正常业务逻辑的构造函数 internal MyDerived() : base(derivedIntList) { } // 供反序列化使用的私有无参数构造函数 private MyDerived() : base() { } // 反序列化完成后触发的回调,初始化基类字段 [OnDeserialized] private void OnDeserialized(StreamingContext context) { InitializeMyIntList(derivedIntList); } }
3. 方案优势说明
- 封装性完全保留:基类的
myIntList仍然是私有字段,派生类无法直接访问,只能通过基类提供的InitializeMyIntList方法合法初始化,完全符合你的权限要求。 - 不可变性保障:通过
IReadOnlyList<int>对外暴露列表,确保外部(包括派生类)无法修改列表内容,完美契合你“构造后永不改变”的设计初衷。 - 职责清晰分离:派生类仅负责提供初始化所需的静态列表,基类负责自身字段的初始化逻辑,代码结构更易维护。
备选方案(不推荐):使用反射设置readonly字段
如果你必须保留myIntList的readonly修饰符,可以通过反射在基类的初始化方法中设置字段值,但这种方式会破坏封装性,且性能较差,仅作为极端场景下的备选:
[DataContract] internal abstract class MyBase { private readonly List<int> myIntList; internal IReadOnlyList<int> MyIntList => myIntList.AsReadOnly(); internal MyBase(List<int> anIntList) { myIntList = anIntList ?? throw new ArgumentNullException(nameof(anIntList)); } protected MyBase() { } protected void SetMyIntList(List<int> list) { if (myIntList != null) return; // 用反射绕过readonly限制设置字段 var field = typeof(MyBase).GetField(nameof(myIntList), BindingFlags.Instance | BindingFlags.NonPublic); field?.SetValue(this, list ?? throw new ArgumentNullException(nameof(list))); } }
此时派生类的OnDeserialized回调只需调用SetMyIntList(derivedIntList)即可,但不建议在生产代码中使用这种方式。
内容的提问来源于stack exchange,提问作者Plunder Bunny
相关产品推荐
相关产品推荐

