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
}
此模式不是全局模式。不是所有实现myICoolSerializable
接口的类都有子部分,也不是所有子部分都继承自任何其他部分。在某些情况下,包装另一个类而不是子类可能是有意义的
一些想法
对于那些感兴趣的人,由于字符串可以隐式转换为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
}
}