6

I want to call a constructor of a struct, that has default values for all parameters. But when I call the parameterless constructor of MyRectangle a not defined constructor get's called. Why is that? Is it possible to not have a not from me created constructor called?

using System;

namespace UebungClasses
{
    class Program
    {
        static void Main(string[] args)
        {
            MyRectangle sixRec = new MyRectangle(3, 2);
            MyRectangle oneRec = new MyRectangle();

            Console.WriteLine("area of six: " + sixRec.Area() + " area of one: " + oneRec.Area());
        }
    }

    public struct MyRectangle
    { 
        public MyRectangle(double w = 1, double l = 1)
        {
            width = w;
            length = l;
            Console.WriteLine("Width: " + width + " Lenght: " + length);
        }

        public double Area()
        {
            return width * length;
        }

        private double width;
        private double length;
    }
}
David Makogon
  • 69,407
  • 21
  • 141
  • 189
InfoMathze
  • 176
  • 6
  • If `MyRectangle` had been a `class` instead of a `struct`, it would have worked the way you intended. But for historical reasons, any use of `new T()` where `T` is a value type, works the same as `default(T)`. – Jeppe Stig Nielsen Jan 21 '18 at 23:16
  • Did you try declaring the parameterless constructor private, such as « private MyRectangle() {} » – santamanno Aug 25 '20 at 16:40

2 Answers2

5

No, basically. If you use new MyRectangle(), it will always prefer the default constructor (meaning: zero init in the case of struct).

If you were dealing with integers, one potential workaround would be to use xor with your desired default in the property accessors - this would mean that when the fields are zero, you get the default values from the accessors. Unfortunately, to do this with double would require using unsafe code to reinterpret the types.

One other potential solution would be to add a dummy parameter that you can use, for example:

new MyRectangle(dummy: true)

This dummy parameter would do literally nothing except allow you to select the custom constructor.

Another final option would be to use a factory method (static MyRectangle Create(...)) instead of the constructor.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Wouldn't you consider a `isNotDefault` flag a viable choice? You'd pay a small penalty on any property access but if its not a problem, its a rather clean way to get custom "default" value behavior in structs. – InBetween Jan 21 '18 at 21:25
  • @InBetween given the choice, I'd prefer the xor approach - an xor is a simple bit-op that pipelines nicely into a CPU chain and will usually out-perform something that relies on a branch – Marc Gravell Jan 21 '18 at 21:27
  • I agree, I was referring to the general case where integers are not involved. – InBetween Jan 21 '18 at 21:31
  • Sad, that i can't do that the way I wanted, but hey, at least I learned something new! Thank You. – InfoMathze Jan 21 '18 at 21:40
  • @InBetween I might be entirely too happy to use `unsafe` code - that would barely slow me down :) – Marc Gravell Jan 21 '18 at 21:47
  • Instead of the XOR thing, you may subtract `1.0`. Something like: `public MyRectangle(double w = 1.0, double l = 1.0) { widthMinusOne = w - 1.0; lengthMinusOne = l - 1.0; }` You would need to add the `1.0` again every time you _read_ the fields (could have a property for that), for example in the `Area` method. – Jeppe Stig Nielsen Jan 21 '18 at 23:26
  • @JeppeStigNielsen perhaps, but xor is a cheaper CPU operation than arithmetic - especially floating point arithmetic; plus there's a chance (when talking about floating point) of that messing with the value due to rounding (especially if it triggers a scale change), where-as xor is guaranteed to be side-effect free – Marc Gravell Jan 22 '18 at 09:15
  • I totally agree. If I were to use my own idea to create a rectangle (smaller than an atomic nucleus in SI units) of width `3.14E-16` and length `2.718E-15`, this subtraction of `1.0` and adding it again would ruin everything. Whereas without this offset thing, the area product `3.14E-16 * 2.718E-15`, will work perfectly with floating points. – Jeppe Stig Nielsen Jan 22 '18 at 11:59
  • This should definitely go in the official documentation. It’s explained too vaguely in default parameters section. It should be added in the constructors with default parameters section. – santamanno Aug 25 '20 at 16:38
1

The issue here is that the compiler will always resolve first to a valid constructor with no default parameters. Its a bit like with generics, where the non generic overload wins, well here, the non default parameter overload is preferred.

Because structs all have a parameterless constructor, that one will be chosen over yours.

A way around this if you need a different default rectangle could be:

public struct MyRectangle
{
    private readonly bool isNotDefault;

    public MyRectangle(double w, double l)
    {
        isNotDefault = true;
        width = w;
        length = l;
    }

    public double Area()
    {
        //Notice the use of the properties,
        //not the backing fields.
        return Width * Length;             
    }

    private readonly double width, length;
    public double Width => isNotDefault ? width: 1;
    public double Length => isNotDefault ? length : 1;

    public override string ToString()
        => $"Width: {Width} Lenght: {Length}";
}

Not sure I'd recommend this for a rectangle, after all a zero sized rectangle seems a reasonable choice, but there are cases where the default value really doesn't make sense. A clear example is a struct representing a rational number, where the default value is a mathematical indetermination: 0/0.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • Yes, sure a default of 0 / 0 is more fitting, but I wondered and "boild down the code" as SE told me to. Thank You for your answer, but I think I will accept the other one. – InfoMathze Jan 21 '18 at 21:39