【问题标题】:Assigning a Readonly Field via Virtual Member in Abstract Class's Constructor在抽象类的构造函数中通过虚拟成员分配只读字段
【发布时间】:2026-01-15 01:45:01
【问题描述】:

我知道在 SO 上已经问过类似的问题,但具体来说,我的问题涉及通过在抽象类的构造函数中调用虚拟成员来设置 readonly 字段的场景。

考虑以下抽象类:

public abstract class FooBase
{
    private readonly IDictionary<string,object> _readonlyCache;

    protected abstract IDictionary<string,object> SetCache();

    protected FooBase()
    {
        _readonlyCache = SetCache();
    }
}

问题:

1) 这只是糟糕的设计吗?

2) 有更好的设计吗?

我知道您可以将FooBase 的实现者声明为sealed,这将确保只调用SetCache() 的正确实现。我不喜欢的是没有办法强制实施者必须标记为sealed。非常欢迎任何建议。

【问题讨论】:

    标签: c# .net oop design-patterns


    【解决方案1】:

    如果可能的话,这绝对是要避免的——在构造函数中调用虚方法总是有点臭,因为你将在子类开始执行初始化之前执行代码——它的构造函数主体不会执行。无论子类是否密封,都是如此;你的处境从根本上来说很糟糕。

    您可能需要考虑让子类构造函数将缓存传递给构造函数:

    public abstract class FooBase
    {
        private readonly IDictionary<string,object> _readonlyCache;
    
        protected FooBase(IDictionary<string,object> cache)
        {
            _readonlyCache = cache;
        }
    }
    

    这样,direct 子类可以决定做什么 - 例如,它可能是抽象的并从进一步派生的类中获取缓存,或者它可能构建自己的。

    【讨论】:

    • 看起来走这条路会强制子类创建一个静态方法来创建缓存。这是一个好方法吗?
    • @Didaxis:不一定要创建静态方法。例如,它可以只使用一个集合初始化器——甚至传递一个空引用,或者对所有实例使用相同的缓存。由子类决定做什么。
    【解决方案2】:

    1) 是的,这是一个糟糕的设计,因为你不能强迫实施者被密封。 (我看不出你怎么能合理地将这种设计决策强加于别人的代码;控制别人的抽象级别不取决于你。)

    2) 是:例如,将ReadOnlyCache 设为抽象或非抽象属性,并在首次使用时调用抽象方法以获取其值:

    private readonly IDictionary<string,object> _readonlyCache;
    
    private IDictionary<string,object> ReadOnlyCache
    {
        get
        {
            return _readonlyCache ?? _readonlyCache = GetEmptyCache();
        }
    }
    
    protected abstract IDictionary<string,object> GetEmptyCache();
    

    【讨论】: