-1

What is the correct way to define a constant value within a derived class in order to validate a parameter in the parent constructor.

Given the following example:

public abstract class Polygon
{
    protected abstract int VertexCount { get; }
    public LineSegment[] Edges { get { /* ... */ } }
    public Point2D[] Vertices { get { /* ... */ } }

    protected Polygon(Point2D vertices)
    {
        if(vertices.Length != VertexCount)
            threw new ArgumentOutOfRangeException(...);
    } 
}

public class Triangle: Polygon
{
    protected override int VertexCount { get; } = 3;
    public Triangle(Point2D[] vertices) : base(vertices){}
}

public class Quadrilateral: Polygon
{
    protected override int VertexCount { get; } = 4;
    public Quadrilateral(Point2D[] vertices) : base(vertices){}
}

Obviously the above does not work as expected due to the Virtual member call in constructor (yes I understand the warning, I'm not asking about that)

In my logic (which seems to be flawed), the VertexCount is a feature of the Polygon class (because the fixed size of the arrays of both Edges and Vertices will be defined by within the Polygon class) during initialization, and varies based on the derived class (hence needs to be defined in the derived class).

The derived class NEEDS to define the value of VertexCount because it is specific to the derived class, however, the base class isn't aware of the derived class. This is the reason for the 'abstract int', to ensure that the derived class overrides it. Also, VertexCount is a property, because fields cannot be abstract. However, it also should be a constant (because a triangle's sides are always going to be 3 and a quadrilateral's sides are always going to be 4).

Finally, the message for the Exception being thrown in the base constructor, should be something like:

if(vertices.Length != VertexCount)
    threw new ArgumentOutOfRangeException(nameof(vertices),
        message: $"A {this.ToString()} requires exactly {VertexCount} vertices, received {vertices.Length}");

Of course the 'this.ToString()' part would be called in the context of the base class rather than the derived class (maybe a reflection solution would work here)?

So with all of this, the alternative is to ditch the constructor in the base class, create a constant for the VertexCount in the derived classes, and initialize and set all the values in the derived classes constructors. This seems like it would be a lot of duplicated code across the derived classes (maybe not in the above example because technically I'm only duplicating 2 lines of code in each, but what happens when the constructor becomes heavier).

What is the 'proper' way of doing this with the least amount of duplicated code?

Aaron Murray
  • 1,920
  • 3
  • 22
  • 38
  • 1
    "proper" is in the eye of the beholder. E.g. it's my opinion that the "virtual property"-based approach is flawed however it's done (yours or the proposal below). You should have `protected` constructors, to which the derived types can pass the appropriate constant value. The value can be stored in a `readonly` field. Don't sweat the extra 4 bytes...it's inconsequential. It's more important to keep the code simple, safe, and easy to reason about (i.e. you shouldn't have to look up esoteric details to understand it). But then, that's just my opinion. As your question is primarily opinion-based. – Peter Duniho Jul 08 '19 at 04:38
  • Oh, and another thing: if you really want to use the virtual approach, why not just _not_ use the virtual member in the constructor? After all, each derived class could in fact do its own parameter validation on the length of the `vertices` parameter, since they after all are the classes that actually know the legal length. Having validated the length, then the base `VertexCount` property can just return the length of the stored vertices array. – Peter Duniho Jul 08 '19 at 04:59
  • @PeterDuniho by _proper_ what I meant is _best practice_ I guess. Usually when I run into a piece of code I am writing that I struggle with, and find a similar SO question that gets me through the struggle, the cause is more often just that I have been looking at the problem / solution from the wrong angle, and the solution often simplifies that code better than the way I was trying to write it. – Aaron Murray Jul 08 '19 at 04:59
  • "proper", "best practice", whatever...either translates directly into an opinion. You may find stronger opinions one way or the other, and people may have supporting facts to justify their opinions, but they are still opinions. I.e. there's no _objective_ "ground truth" regarding what will or will not work in a program. Just varying approaches that all accomplish the same thing. – Peter Duniho Jul 08 '19 at 05:01
  • @PeterDuniho yes and I understand that I can validate from the derived class, and technically that is really where it _should_ go from a domain viewpoint anyhow. At the same time, when it is exactly the same predicate with the exception of the facts (ie. number of vertices) for each derived class it would be nice to wrap the validation in the base and have it fire first automatically leaving derived class constructors to handle specific initialization (cleaner). Consider a similar example where there are many more 'moving parts'. – Aaron Murray Jul 08 '19 at 05:18
  • Well, with what you are saying, that's programming in general. We write code the way we think it should be written for the situation. Me personally, sometimes I start to sense that the way I am writing something just doesn't seem right, I look for _opinions_, to see if there are better ways to handle it. – Aaron Murray Jul 08 '19 at 05:33
  • _"that's programming in general"_ -- no, not at all. The vast majority of programming, is just about getting the job done. Languages and frameworks generally dictate the _only_ correct means of accomplishing something, and when they don't, most of the time people don't spend time worrying about whether one variation is significantly better than the other, because most of the time, one isn't. And questions about those objective, fact-based scenarios are on-topic here. Questions primarily based in opinion are not. – Peter Duniho Jul 08 '19 at 06:05
  • _"when it is exactly the same predicate with the exception of the facts (ie. number of vertices) for each derived class it would be nice to wrap the validation in the base"_ -- feel free to create a helper method in the base that takes the expected and actual values, and throws an exception if they don't match. But either way, I don't see how requiring a declaration of an override in the derived classes would be preferable. All that said, the very fact that we are having this discussion just proves my point about the opinion-based nature of this question. This is exactly why it's off-topic. – Peter Duniho Jul 08 '19 at 06:08
  • 1
    Well. Another point here: What if one day you decide to load a dynamic polygon (i.e. a sprite) from a file? Clearly this polygon would not be subjected to validation since it is dynamic. – ph3rin Jul 08 '19 at 09:22
  • That is a good point, more reason to keep the validation with the derived class. Refactoring, I removed the VertexCount altogether and created a method in the base class `private bool HasCorrectVertexCount(int num)`, which I am calling from the beginning of the derived class. The downside is that instantiation will go through the base class constructor before validation. Getting rid of VertexCount on the other hand also frees up memory. – Aaron Murray Jul 08 '19 at 13:51

1 Answers1

0

After re-reading Peter Duniho's comments I came up with this refactoring which I believe is a much better solution. (I think the wording of the question was a little ambiguous/subjective because the logic was off initially and I was looking for a correction to my logic as opposed to knowing exactly what the problem was and how to fix that problem)

The question:

What is the correct way to define a constant value in a derived class used to validate parameters in a parent constructor in C#

Short answer, you don't. Defining a constant in the above sample code is not necessary. It doesn't need to be stored at all, it is not something that other parts of the code (in this case) need, it is a value that is only needed for validation upon instantiation.

public class Triangle: Polygon
{
    public Triangle(Point2D[] vertices) : base(vertices, 3){}
}

public class Quadrilateral: Polygon
{
    public Quadrilateral(Point2D[] vertices) : base(vertices, 4){}
}

The other goal that I was trying to accomplish (with the original example), was early validation. In other words, if the correct number of vertices is not passed for the derived type, I want to stop initialization before any other initialization steps are performed on an invalid object. In the given example, an empty default constructor and validation within the derived constructor (or even a call to a validation method contained in the base method from the derived constructor) would be fine, not passing the vertices to the base, rather validating and then setting them in the derived constructor. However, the key is early validation, what if the base class does a bunch of stuff (a more complex class) to (pre)initialize the object before getting to the derived class. Validation code really belongs before any processing (including initialization).

By passing a (validation related) value to the base constructor allows the validation to occur at the beginning, so as to prevent other code from running if the validation fails.

protected Polygon(Point2D[] vertices, int expectedVerticesCount)
{
    // Don't bother initializing the object, the data is invalid!
    if(vertices.Length != expectedVerticesCount)
        throw new ArgumentOutOfRangeException( /* ... */);

    // The data is valid, proceed with initialization
    Vertices = vertices;
}
Aaron Murray
  • 1,920
  • 3
  • 22
  • 38
  • I am sure there are other ways to handle this (and I am open to other answers or comments). Programming is often not black and white, but when you start feeling like your code is getting 'hacky' to achieve something, there is probably a better solution. Sometimes it is difficult to provide 'explicit' questions, so providing code examples allows for questions to be implied and maybe answers (and comments, including clarification questions), provide value from other perspectives when other similar code is being written. We can't all be programming masters, thats why we come here to learn. – Aaron Murray Jul 08 '19 at 15:10