C# 确保派生类不引用基类中的属性

C# 确保派生类不引用基类中的属性,c#,inheritance,C#,Inheritance,目的 为了单元测试的目的,如何通过任何方法确保派生类不引用基类中的任何属性?我明白。我能否以某种方式创建基类的模拟,并观察是否在错误的时间调用了属性?还是其他方式 背景 我有一系列参与序列化的类。部分和片段有一个自然的层次结构,例如,Chunk1知道如何序列化自身(开始、结束、分隔符),但会将内部部分的序列化委托给本身序列化多行的Blob 以下是所有部件实现的接口: public interface ICoolSerializable { void Serialize(Writer w);

目的

为了单元测试的目的,如何通过任何方法确保派生类不引用基类中的任何属性?我明白。我能否以某种方式创建基类的模拟,并观察是否在错误的时间调用了属性?还是其他方式

背景

我有一系列参与序列化的类。部分和片段有一个自然的层次结构,例如,
Chunk1
知道如何序列化自身(开始、结束、分隔符),但会将内部部分的序列化委托给本身序列化多行的
Blob

以下是所有部件实现的接口:

public interface ICoolSerializable {
   void Serialize(Writer w);
}
并给出了所需的序列化结果:

Chunk1:/Line1
/Line2
有一个
Chunk1
类负责“Chunk1:”并从
Blob
类继承,后者依次负责“/Line1”、换行符和“/Line2”。(两者都实现了ISerializable)

注意:为了回答这个问题,请假设我确实想要一个is-a关系,
Chunk1
继承自
Blob
(可以在许多不同的块中使用
Blob
,而
Chunk1
只确定如何解释
Blob
,而不是如何在初始标签之外序列化)

问题

我认为我或其他开发人员将来可能会遇到这样的问题:编写更多类似这样的类并尝试复制模式。由于
Chunk1
的构造函数接受
IEnumerable
Line
项传递到它的基
Blob
,开发人员将记住基是如何构造的,并且在
Chunk1
serialize方法中可能很容易犯此错误:

public override void Serialize(Writer w) {
   w.Write("Chunk1:");
   w.WriteEnumerable(Lines); // wrong, this is a forbidden base.Lines!
}
这将产生错误的序列化结果(缺少斜杠):

完全公开:我确实犯了这个错误,然后最初通过在派生类的每一行之前写“/”来“修复”它。当然,当另一个类从基继承时,它也丢失了斜杠——我以错误的方式修复了它

问题

那么,我如何检查
Serialize
方法,或者采取任何其他措施来确保
base.Lines
永远不会从其内部访问?与其采用上述错误的方式,不如按照以下方式工作:

public override void Serialize(Writer w) {
   w.Write("Chunk1:");
   base.Serialize(w); // Remember to let the superclass decide how to serialize itself
}
此模式不是全局模式。不是所有实现my
ICoolSerializable
接口的类都有子部分,也不是所有子部分都继承自任何其他部分。在某些情况下,包装另一个类而不是子类可能是有意义的

一些想法

对于那些感兴趣的人,由于字符串可以隐式转换为
ICoolSerializable
,我希望我可以这样做:

public override void Serialize(Writer w) {
   w.WriteCoolSerializables,
      "Chunk1:",
      base
   }
}
但是,
base
在这里不能引用基实例,如果我将当前类强制转换为其父类,它仍然无法工作,因为派生的
Serialize
方法(它是
override
!)将被调用,从而导致循环,最终导致堆栈溢出

更新


我怀疑正确的答案是重构,但我不确定重构现在将如何工作。我怀疑我可能更倾向于反射,以及通过属性或返回的一系列属性或值访问对象的序列化过程,而不是过程语句。这将使属性访问要检查的对象以查看它们所引用的内容。这还可以使父类(通过属性或返回信息的类似属性的方法)指示它与任何子类的关系,这是一种模板,表示“子类只能在头部钩住我的序列化组件”,然后可以强制执行。

将基类中的属性设置为私有。

在这种情况下,我不会使您的
序列化方法可继承

protected void SerializeCore(Writer w) { }
public void Serialize(Writer w) {
    SerializeCore(w);
    ...
}
通过这种方式,您可以控制基类的序列化方式。如果您希望更严格,可以使用带有属性的反射来执行序列化

属性的基类示例:

public abstract class CustomSerializeAttribute : Attribute
{
    public abstract void SerializeProperty(Writer w, object value);
}

如果您愿意将属性提供的功能包装为函数,则可以对照黑名单或白名单检查调用方的源文件,黑名单或白名单中的文件不能/可以包含访问这些属性的代码

Blob
实现中,对于要监视的每个属性(包装器),可以执行以下操作:

public int GetExampleProp([System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "")
{
    CheckCaller(sourceFilePath);
    return ExampleProp;
}
public void SetExampleProp(int value, [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "")
{
    CheckCaller(sourceFilePath);
    ExampleProp = value;
}
然后在
CheckCaller

private void CheckCaller(string path)
{
    if (!_whitelist.Contains(path)) {
        // report error
    }
}

它们不能是私有的,因为当消费者使用我的
Chunk1
对象时,它需要能够读取这些属性。例如,在反序列化时,业务层将获得一个完全构造的对象,查看提供的块列表中的
Chunk1
,并知道它可以读取(例如)
IEnumerable Lines
属性以获取其业务数据。如果没有公共属性,则该类将无效。在这种情况下,您可以使用某种代理。如果在单元测试中再次子类Chunk1并重写Lines属性getter,您可以跟踪Lines属性被访问的次数。这不会影响要求您将行虚拟化,Chunk1无法密封。这种方法并不理想,但ORMs和模拟框架经常使用这种方法。显然,如果您想将其应用于Blob的所有子类,那么您需要使用一些非常可怕的反射进行子类化,或者使用类似castle proxy的库。此外,您是否考虑过使用自定义static分析规则,而不是增加单元测试?这听起来是一个合理的途径。我得去研究一下
private void CheckCaller(string path)
{
    if (!_whitelist.Contains(path)) {
        // report error
    }
}