Reputation: 50231
Purpose
How do I through any method, for the purposes of a unit test, make sure that a derived class does not refer to any properties in the base class? I understand that Reflection won't cut it, here. Could I create a mock of the base class somehow and observe if a property is called at the wrong time? Or any other way?
Background
I have a series of classes that are participating in serialization. There is a natural hierarchy of parts and pieces, so that for example, a Chunk1
knows how to serialize itself (starting, ending, delimiters) but will delegate the serialization of its inner parts to a Blob
that itself serializes several lines.
Here is the interface that all the parts implement:
public interface ICoolSerializable {
void Serialize(Writer w);
}
And given this desired serialization result:
Chunk1:/Line1
/Line2
There is a Chunk1
class that is responsible for "Chunk1:" and inherits from the Blob
class, which in turn is responsible for "/Line1", the newline, and "/Line2". (Both implement ISerializable
.)
Note: please assume for the sake of the question that I truly do want an is-a relationship, and it is correct for the Chunk1
to inherit from the Blob
(the Blob
can be used in many different chunks, and the Chunk1
just determines how the Blob
is interpreted, but not how it is serialized beyond the initial label).
The Problem
I see a potential gotcha for me or another developer in the future writing more classes like this and attempting to copy the pattern. Since the constructor of Chunk1
accepts an IEnumerable
of Line
items to pass to its base Blob
, the developer will have in mind how the base is constructed, and might easily make this mistake in the Chunk1
serialize method:
public override void Serialize(Writer w) {
w.Write("Chunk1:");
w.WriteEnumerable(Lines); // wrong, this is a forbidden base.Lines!
}
This would yield the wrong serialization result (missing the slashes):
Chunk1:Line1
Line2
Full disclosure: I did make this mistake, and then initially "fixed" it by writing "/" before each Line from the derived class. Of course, the moment another class inherited from the base, it also was missing the slashes—I'd fixed it the wrong way.
The Question
So how can I inspect the Serialize
method or take any other measure to ensure that base.Lines
is never accessed from within it? Instead of the wrong way above, it needs to work like this:
public override void Serialize(Writer w) {
w.Write("Chunk1:");
base.Serialize(w); // Remember to let the superclass decide how to serialize itself
}
This pattern is not global throughout. Not all classes implementing my ICoolSerializable
interface have sub-parts, nor do all of them inherit from anything else. In some cases, it may make sense to wrap another class instead of subclass from it.
Some Thoughts
For those interested, since strings are implicitly convertible to ICoolSerializable
, I wish I could do this:
public override void Serialize(Writer w) {
w.WriteCoolSerializables,
"Chunk1:",
base
}
}
However, base
here cannot refer to the base instance, and if I cast the current class as its parent, it still wouldn't work because the derived Serialize
method (it's override
!) would be called and thus cause a loop, eventually resulting in a stack overflow.
Update
I suspect that the right answer will be refactoring, but I'm not sure how that refactoring will work right now. I suspect that I may lean more heavily on Reflection, and on the serialization process working through properties or a returned series of property-or-value-accessing objects, rather than on a procedural statement. This would enable the property-accessing-objects to be inspected to see what they're referring to. This could also enable the parent class to indicate (through attributes or an attribute-like method that returns information) how it relates to any child class, a sort of template that says "the child class may only hook onto my serialization components at the head", which can then be enforced.
Upvotes: 1
Views: 222
Reputation: 650
If you are willing to wrap the functionality provided by your properties with functions, you could check the caller's source file against a blacklist or whitelist of which files can't/can contain code that accesses those properties.
Within the Blob
implementation, for each property (wrapper) you want to monitor you can do something along these lines:
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;
}
and then check if the call is valid in CheckCaller
private void CheckCaller(string path)
{
if (!_whitelist.Contains(path)) {
// report error
}
}
Upvotes: 0
Reputation: 1129
In this case I wouldn't make your Serialize
method inheritable.
protected void SerializeCore(Writer w) { }
public void Serialize(Writer w) {
SerializeCore(w);
...
}
This way you control how your base class is serialised. If you want to be stricter you could use reflection with attributes to perform serialisation.
Example base class for the attributes:
public abstract class CustomSerializeAttribute : Attribute
{
public abstract void SerializeProperty(Writer w, object value);
}
Upvotes: 1