简体   繁体   中英

Better design for overloaded constructors?

I've been playing around with constructors and noticed in most code overloaded constructors are:

public class ClassFirst
    {
        public string Name { get; set; }
        public int Height { get; set; }
        public int Weight { get; set; }

        public ClassFirst(string name, int height, int weight)
        {
            Name = name;
            Height = height;
            Weight = weight;

        }

        public ClassFirst(string name)
            : this(name, 0, 0)
        { }

        public ClassFirst(string name, int height)
            : this(name, height, 0)
        { }

    } 

Which I would call 'underloading' instead of overloading, because the added constructors chip away at the full constructor... and seems to be used much more than the way I intuitively want to overload, which is ->

 public class ClassSecond
    {
        public string Name { get; set; }
        public int Height { get; set; }
        public int Weight { get; set; }

        public ClassSecond(string name)
        {
            Name = name;

        }

        public ClassSecond(string name, int height): this (name)
        {

            Height = height;    
        }

        public ClassSecond(string name, int height, int weight): this (name, height)

        {
            Weight = weight;
        }
}

Why are the constructors is used like that? There must be advantages...

Answer: below is a great example of how to concisely write overloaded constructors in .net 4.0 using default parameters.

Previous Stack Overflow Answer: I found there is a previous question that addresses constructor overloads: C# constructor chaining? (How to do it?)

Do the overloading - but supply the default parameters in the default constructor (or lowest level as the case may be):

public class ClassSecond
{
    public string Name { get; set; }
    public int Height { get; set; }
    public int Weight { get; set; }

    public ClassSecond(string name)
    {
        Name = name;
        Height = 100;
        Weight = 100;
    }

    public ClassSecond(string name, int height)
        : this(name)
    {
        Height = height;
    }

    public ClassSecond(string name, int height, int weight)
        : this(name, height)
    {
        Weight = weight;
    }
}

Due to the order of which constructors are called, you'll be setting the variables to their default values, and then later overriding them with the user-specified values. This is valid as long as your properties aren't executing some sort of logic against the setter.

That being said, as you've posted an answer I would assume you're okay with .Net 4 default parameters. In which case all of that could be replaced with:

public MyClass(string name = "Ben", int height = 100, int weight = 20)
{
    Name = name;
    Weight = weight;
    Height = height;
}

This will contain the functionality of all the overloads you've built within your question and then some.

Examples (all valid code that perform as you'd expect):

MyClass a = new MyClass();
MyClass b = new MyClass("bob");
MyClass c = new MyClass("bob", 100);
MyClass d = new MyClass("bob", 141, 300);
MyClass e = new MyClass("bob", weight: 300);
MyClass f = new MyClass(height: 50);

The first one is better, because it allows you to have default values which are different then 0 / false / null .

Consider following

public ClassFirst(string name, int height, int weight)
{
    Name = name;
    Height = height;
    Weight = weight;
}

public ClassFirst(string name, int height)
    : this(name, height, 150)
{ }

and now with your approach

public ClassSecond(string name, int height)
{
    Height = height;    
}

public ClassSecond(string name, int height, int weight): this (name, height)
{
    Weight = weight;
}

Where would you put 150 as default weight value? You could try

public ClassSecond(string name, int height)
{
    Height = height;
    Weight = 150;
}

but hey, we're overwriting user-specified value which was assigned in previous constructor! Conditional default value?

public ClassSecond(string name, int height)
{
    Height = height;
    Weight = Weight != 0 ? Weight : 150;
}

Looks much less readable, and yet will set weight to default value when you call ClassSecond("name", 100, 0) .

Of course, you can use non-automatic properties and set default values into fields declaration directly:

private int _weight = 150;
public int Weight
{
    get { return _weight; }
    set { _weight = value; }
}

public ClassSecond(string name, int height)
{
    Height = height;    
}

public ClassSecond(string name, int height, int weight): this (name, height)
{
    Weight = weight;
}

But that's much more code, and it's not that obvious what's going on (you have to look at properties and fields declarations to check if default values are set, and what are the default values.

While it is overloading, this technique is also known as Constructor Chaining , and it has clear advantages. The most immediate advantage is that it follows the DRY (Don't Repeat Yourself) principle.

This becomes easier to see if you also start protecting the invariants of the class by adding Guard Clauses :

public string Name { get; private set; }
public int Height { get; private set; }
public int Weight { get; private set; }

public ClassFirst(string name, int height, int weight)
{
    if (name == null)
        throw new ArgumentNullException("name");
    if (height < 0)
        throw new ArgumentOutOfRangeException("height");
    if (weight < 0)
        throw new ArgumentOutOfRangeException("weight");

    Name = name;
    Height = height;
    Weight = weight;
}

public ClassFirst(string name)
    : this(name, 0, 0)
{ }

public ClassFirst(string name, int height)
    : this(name, height, 0)
{ }

The alternative is to repeat these Guard Clauses in each and every overloaded constructor. The more you add to a constructor, the greater becomes the risk that you forget to add the logic in one of the overloaded constructors.

Constructor Chaining protects you against making mistakes like that.

Thanks for the constructive posts to my question. As a result I'm 'scouting' the prospects of applying the 'new' 4.0 default parameter values to constructors to solve the problem of default values that MarcinJuraszek points above. So my class looks like:

public class ClassSecond
    {
        public string Name { get; set; }
        public int Height { get; set; }
        public int Weight { get; set; }


        public ClassSecond(string name = "Ben")
        {
            Name = name;

        }

        public ClassSecond(string name = "Ben", int height = 100): this (name)
        {

            Height = height;    
        }

        public ClassSecond(string name = "Ben", int height = 100, int weight = 20): this (name, height)

        {
            Weight = weight;
        }







    }

and I get intellisense support like

Intellesenes1在此输入图像描述在此输入图像描述

Are there drawbacks that I should become aware of? Does this succumb to or circumvent the versioning issues of default parameters?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM