|
|
|
We haven't look at constructors very much since we started into inheritance. This is because they are a little more complex, so it's important that you understand how inheritance works before tackling them. With that behind us, however, let's take a look at a few constructors, using inheritance. We'll start with three constructors in the same class... class Employee { private string _name; private int _id; private double _salary; public Employee(): this("", 0, 0.0) { } public Employee(string name, int id): this(name, id, 0.0) { } public Employee(string name, int id, double salary) { _name = name; _id = id; _salary = salary; } } We are now using "overloaded" constructors, in much the same way that we can use overloaded methods (and I'm talking about overloading, not overriding). The only constructor that's actually assigning values to the object's members is the Employee(string, int, double) constructor. The other two just call the constructor with the most parameters, by calling "this" as a method. They repeat the values of any variables that were passed to them, and provide default "zero values" for the ones they didn't get. For example, the Employee(string, int) constructor is passed a name and id. In turn, it passes these values on to the Employee(string, int, double) constructor. Since we weren't passed a salary, however, we simply pass a 0.0 value for that parameter. The default constructor, by contrast, wasn't passed any information. It calls the Employee(string, int, double) constructor, and passes in nothing but "zero values". Now, we don't have to do things this way. In theory, there's nothing wrong with doing this... class Employee { private string _name; private int _id; private double _salary; public Employee() { _name = ""; _id = 0; _salary = 0.0; } public Employee(string name, int id) { _name = name; _id = id; _salary = 0.0; } public Employee(string name, int id, double salary) { _name = name; _id = id; _salary = salary; } } ...it's just that it gets a little repetitive to write the full code for each constructor, when the only thing that's actually different for each one is the values that are provided. Besides, if your constructor has a fairly complex and/or lengthy set of code, and you have ten different constructors, do you really want to duplicate that thing in each one? You're just begging for an infamous "copy and paste" error (these happen when you copy & paste a large block of code, with the intent of just changing a few variables or names, but you change the wrong ones or forget to change one of them). And what if you need to make a change in the code? You'll have to make that change the same way in every constructor, which raises the possibility of even more errors! Good OOP, and for that matter, good programming in general, should seek to prevent needless code duplication wherever possible. So, now we've got a base class with three different constructors in it. Did you know that an inheriting class will be able to make calls to those constructors, too? Check this out: class Accountant: Employee { private int _beansCounted; public Accountant() { _beansCounted = 0; } public Accountant(string name, int id, int beansCounted) : base(name, id, beansCounted) { } public Accountant(string name, int id, double salary, int beansCounted) :base(name, id, salary) { _beansCounted = beansCounted; } } Calling the base class's constructors works exactly the same way as calling constructors in our own class. We just use "base" instead of "this" to make the call. Also notice that we are free to break the call into as many lines as we want, which is nice, because putting the whole thing on one line could get pretty lengthy. The advantage of calling the base constructor, of course, is that we don't have to re-write the code in the base constructor again. We can call it from the inherited class, and let it handle the base class' members for us. Of course, we could also assign to the base class's members from an inherited constructor, but the hope is that calling the base class' constructor will do this for us. Note that the code in the base constructor runs before the code in the inherited constructor. Now, don't get the idea that because Employee has a two-parameter constructor, Accountant needs one, too. The number of actual parameters in our Accountant constructors are irrelevant, as long as we can pass something back to the base class that matches one of its constructors' signatures. For example, we could actually do this... public Accountant(int id, int beansCounted) :base("Clark", id, 45.0) { _beansCounted = beansCounted; } ...and as long as we don't mind that every Accountant we create from this constructor will have a name of "Clark" and a salary of 45.0, there's nothing wrong with this. In fact, as long as the base class has a default (no parameters) constructor, we don't need to make any calls to the base class at all. We can just write out the inherited class's constructor as normal, and the program will implicitly run the base class's constructor without even being asked: public Accountant(int beansCounted) // base class's default constructor called here { _beansCounted = beansCounted; } This whole time, the inherited class's constructors have actually been making this implicit call to the base class, without you even having to know about it. Because the call to the base class's default constructor doesn't use any parameters, C# lets you get away with not writing it at all. When you actually write the call to base, you take control of this process yourself, and have the opportunity to tell C# which specific constructor in base you want it to call. This process can get a little more tricky when our base class doesn't have a default constructor. If this is the case, each of the inherited class's constructors must explicitly make a call to one of the base class' constructors. The implicit call to the default constructor (shown just above) won't work, because there is no default constructor to implicitly call. The code sample below defines two inheritance relationships: an Employee/Accountant relationship (with a default constructor in Employee), and an Animal/Bird relationship (with no default constructor in Bird). Each constructor is heavily commented, to explain exactly what's going on. As a bonus, I've added WriteLine() statements that will allow you to run this program, and see the exact order that the code executes in when creating the objects through their various constructors. using System; namespace D_AdvancedConstructors { class Class1 { static readonly string ln = Environment.NewLine; [STAThread] static void Main(string[] args) { Console.WriteLine("ACCOUNTANT ONE:"); Accountant acc1 = new Accountant(); Console.WriteLine(ln + "ACCOUNTANT TWO:"); Accountant acc2 = new Accountant("Gary", 5, 7236); Console.WriteLine(ln + "ACCOUNTANT THREE:"); Accountant acc3 = new Accountant("Bruno", 6, 45.0, 18000); Console.WriteLine(ln + "BIRD ONE:"); Bird bird1 = new Bird(); Console.WriteLine(ln + "BIRD TWO:"); Bird bird2 = new Bird("Quail", 5.0, 20); PromptForExit(); } static void PromptForExit() { Console.Write(ln + "Program complete! Hit enter to exit..."); Console.ReadLine(); } } class Employee { private string _name; private int _id; private double _salary; // "defualt" constructor actually calls specific // constructor, passing in generic values public Employee(): this("", 0, 0.0) { Console.WriteLine("Entering Employee()"); Console.WriteLine("Exiting Employee()"); } // "semi-specific" constructor calls specific // constructor, passing in generic information for // any variables not supplied public Employee(string name, int id): this(name, id, 0.0) { Console.WriteLine("Entering Employee(string, int)"); Console.WriteLine("Exiting Employee(string, int)"); } // "specific" constructor // No need for any other constructor to perform assignments // to object members, as long as they call this method (which // does it for them). public Employee(string name, int id, double salary) { Console.WriteLine("Entering Employee(string, int, double)"); _name = name; _id = id; _salary = salary; Console.WriteLine("Exiting Employee(string, int double)"); } } class Accountant: Employee { private int _beansCounted; // "default" constructor implicitly calls default // constructor in base class, which fills in all the // base class's members with default values. We still // need to handle the members specific to this class, though. public Accountant() { Console.WriteLine("Entering Accountant()"); _beansCounted = 0; Console.WriteLine("Exiting Accountant()"); } // "semi-specific" constructor calls an appropriate "semi-specific" // constructor in base class. Let's pass beansCounted up to the // the specific class, so that we only need to handle it once. public Accountant(string name, int id, int beansCounted) : base(name, id, beansCounted) // feel free to break this into several lines { Console.WriteLine("Entering Accountant(string, int, int)"); Console.WriteLine("Exiting Accountant(string, int, int)"); } // "specific" constructor calls the specific constructor // in base class public Accountant(string name, int id, double salary, int beansCounted) :base(name, id, salary) { Console.WriteLine("Entering Accountant(string, int, double, int)"); _beansCounted = beansCounted; Console.WriteLine("Exiting Accountant(string, int, double, int)"); } } class Animal { private string _species; private double _height; private double _weight; // In this class, we have not defined a "default" constructor. // Therefore, every constructor in an inheriting class must // supply, at a minimum, the three parameters in this constructor. public Animal(string species, double height, double weight) { Console.WriteLine("Entering Animal(string, double, double)"); _species = species; _height = height; _weight = weight; Console.WriteLine("Exiting Animal(string, double, doublet)"); } } class Bird: Animal { private double _wingSpan; // Although there is no default constructor in the base class, // we are still permitted to have a "parameterless" constructor // here... however, it must still call one of the base class's // constructors; with default "hard-coded" values, if necessary. // Of course, this constructor is still responsible for handling // its own members - in this case, "wingspan". public Bird() :base("", 0.0, 0.0) { Console.WriteLine("Entering Bird()"); _wingSpan = 0.0; Console.WriteLine("Exiting Bird()"); } // Just to drive this idea home, here is a constructor that accepts // variables for two of the base class's members, but not the third. // We pass the two known variables into the base class's constructor, // and just pass a hard-coded 0.0 for the other one. This constructor // also accepts a value for the wingspan. public Bird(string species, double weight, double wingSpan) :base(species, 0.0, weight) { Console.WriteLine("Entering Bird(string, double, double)"); _wingSpan = wingSpan; Console.WriteLine("Exiting Bird(string, double, double)"); } } } |
|