You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

保留严格访问修饰符同时实现类序列化的方法

解决方案:保持基类封装性的同时实现反序列化初始化

要解决这个问题,核心思路是让基类自己负责私有字段的初始化逻辑,同时通过受保护的方法给派生类提供合法的初始化入口,完全避免弱化基类成员的访问权限。以下是具体实现步骤和代码示例:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:48:20