C# – Same Constructor Signature with Different Semantics

cclass-designdata structures

Occasionally, I'm confronted with the problem that I have to provide some constructors in order to initialize objects with different sets of data. These sets can be mathematically transformed into each other, for example:

        public Element(int x, int y, int width, int height)
        {
            setValues(width, height, x, y);
        }

        public Element(int RectTop, int RectLeft, int RectRight, int RectBottom)
        {
            setValues(RectRight - RectLeft, Math.Abs(RectBottom - RectTop), RectLeft, -RectTop);
        }

With such an desired overloading, which actually isn't any because of the same signatures, it is probable that the wrong constructor is invoked, e.g. you provide the Rect-Values and the first constructor is invoked. Assuming, we would have different types, we could achieve it by swapping types with good knowledge that this is a bad practice. But that's simply not the case in this easy example with four times int type.

Can you think of some elegant alternatives to achieve the desired behavior that we have the convenience of overloading, but with the same signature?

Best Answer

public Element(Point topLeft, Size size)

public Element(Point topLeft, Point bottomRight)

You could also use factory methods

public static Element FromLeftTopWidthSize(int left, int top, int width, int height)

public static Element FromLeftTopRightBottom(int left, int top, int right, int bottom)

Or you can use a fluent interface

Element.Top(10).Left(28).Right(112).Bottom(101);

Element.Top(10).Left(28).Width(102).Height(73);

You can easily use interfaces to force Left after Top, Right or Width after Left, Bottom after Right, and Height after Width. (see below)

Or, you can do the most sensible thing of all and not allow the different choices. Just choose one and stick to it.


Example of a Progressive Fluent Interface:

public class ElementBuilder : ITopSyntax, ILeftSyntax, IRightSyntax, IWidthSyntax
{
    private int _top;
    private int _left;
    private int _right;

    public ElementBuilder(int top)
    {
        _top = top;
    }

    ILeftSyntax ITopSyntax.Left(int left)
    {
        _left = left;
        return this;
    }

    IRightSyntax ILeftSyntax.Right(int right)
    {
        _right = right;
        return this;
    }

    IWidthSyntax ILeftSyntax.Width(int width)
    {
        _right = width + _left;
        return this;
    }

    Element IRightSyntax.Bottom(int bottom)
    {
        return new Element(_left, _top, _right, bottom);
    }

    Element IWidthSyntax.Height(int height)
    {
        return new Element(_left, _top, _right, _top + height);
    }
}

internal interface ILeftSyntax
{
    IRightSyntax Right(int right);
    IWidthSyntax Width(int width);
}

internal interface IRightSyntax
{
    Element Bottom(int bottom);
}

internal interface IWidthSyntax
{
    Element Height(int height);
}

internal interface ITopSyntax
{
    ILeftSyntax Left(int left);
}