Is it okay for a struct to have default value invalid?

Consider the following struct

public struct ResizeFactor
{
    public readonly float Horizontal;
    public readonly float Vertical;

    public ResizeFactor(float horizontal, float vertical)
    {
        if (horizontal <= 0)
            throw new ArgumentOutOfRangeException("...");
        if (vertical <= 0)
            throw new ArgumentOutOfRangeException("...");

        Horizontal = horizontal;
        Vertical = vertical;
    }
}

It feels like a 'value'. It's small and immutable. It looks and feels 'struct'y.

But there's the default value problem... It doesn't feel right to have ResizeFactor with Horizontal = Vertical = 0 as default value.

Example:

var myResizeFactor = new ResizeFactor();
// myResizeFactor.Horizontal = 0

I know I could use a class instead and just 'disable' the default parameterless constructor, but that's not really the point here. The question is: is it legit to create such struct? One with a invalid default value?

Last but not least: it this the right place to a question like this? Or should I post somewhere else, like https://softwareengineering.stackexchange.com/

Jon Skeet
people
quotationmark

I would try very hard to make the default value valid, yes. For example, you could do something like:

public struct ResizeFactor
{
    private readonly float horizontal;
    private readonly float vertical;

    public float Horizontal => horizontal == 0f ? 1.0f : horizontal;
    public float Vertical => vertical == 0f ? 1.0f : vertical;

    public ResizeFactor(float horizontal, float vertical)
    {
        if (horizontal <= 0)
            throw new ArgumentOutOfRangeException("...");
        if (vertical <= 0)
            throw new ArgumentOutOfRangeException("...");

        this.horizontal = horizontal;
        this.vertical = vertical;
    }
}

This means that the default internal representation has an effective value of (1f, 1f) — a no-op resize, basically.

We use the same approach in Noda Time where various value types can contain a reference to a time zone — which we deem to be UTC if the value is null.

Note that this is one of the benefits of exposing properties instead of fields... the internal details become less relevant.

As an aside, I think actually a "resize to 0" is entirely reasonable as a factor. After all, if you allow float.Epsilon that would resize almost anything to either 0 or very, very small anyway... is there really that much difference between that and resizing to 0?

people

See more on this question at Stackoverflow