ErikE
ErikE

Reputation: 50231

Ensure derived class does not refer to properties in the base class

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

Answers (3)

jwde
jwde

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

pmccloghrylaing
pmccloghrylaing

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

Dave C
Dave C

Reputation: 231

Make the properties in the base class private.

Upvotes: 0

Related Questions