Constructor and this Keyword 2020
The class type is the most fundamental programming construct in the .NET platform.
A class is a user-defined type that is composed of field data (member variables and members that operate on this data such as constructors, properties, method, events, and so forth. The set of field data represents the state of a class instance (object). By grouping data and related functionality in a class definition, we are able to model our software after entities in the real world.
Let's create a new C# Console Application SimpleClasses. Then, insert a new class file, ToyCar.cs into our project using the Project -> Add Class menu. Choose the Class icon from the resulting dialog box and click Add button.
class ToyCar { public string name; public int speed; }
The member variables are declared using public access modifier. Public members of a class are directly accessible once an object (instance of a given class) of this type has been created. After we have defined the set of member variables that represent the state of the type, the next thing is to establish the members that model its behavior. Our ToyCar class need two methods: SpeedUp() and PrintState():
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string name; public int speed; public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", name, speed); } public void SpeedUp(int inc) { speed += inc; } static void Main(string[] args) { ToyCar myToy = new ToyCar(); myToy.name = "Boong Boong"; myToy.speed = 5; for (int i = 0; i < 5; i++) { myToy.SpeedUp(10); myToy.PrintState(); } Console.WriteLine(); } } }
Output is:
Boong Boong's speed is 15 mph. Boong Boong's speed is 25 mph. Boong Boong's speed is 35 mph. Boong Boong's speed is 45 mph. Boong Boong's speed is 55 mph.
Objects must be allocated into memory using the new keyword. If we don't use the new keyword and attempt to use our class variable in a subsequent code, we'll get a compiler error, something like Use of unassigned local variable:
static void Main(string[] args) { ToyCar myToy; myToy.name = "Boong Boong"; }
To correctly create a class type variable, we should define and allocate a ToyCar object on a single line of code:
static void Main(string[] args) { ToyCar myToy = new ToyCar(); myToy.name = "Boong Boong"; }
If we want to define and allocate an object on separate lines of code, we may do as following:
static void Main(string[] args) { ToyCar myToy; myToy = new ToyCar(); myToy.name = "Boong Boong"; }
In the example, the first code simply declares a reference to a yet-to-be-determined ToyCar object. It is not until we assign a reference to an object view the new keyword that this reference points to a valid class instance in memory.
Now that objects have state, the object user will typically want to assign relevant values to the object's field data before they use it. The ToyCar type demands that the name and speed fields be assigned. For our example, we have only two public data. However, in case when a class have dozens of fields, it would be undesirable to author 20 initialization statements to set 20 data.
But thanks to the class constructors, which allow the state of an object to be established at the time of creation. A constructor is a special method of a class that is called indirectly when creating an object using the new keyword. However, unlike a normal method, constructors never have a return value (not even void and are always named identically to the class they are constructing.
Every class is provided with a default constructor that we may redefine. A default constructor never takes argument, by definition. Beyond allocating the new object into memory, the default constructor ensures that all field data is set to an appropriate default value. If we can not satisfied with these default assignments, we may redefine the default constructor to meet our needs.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string name; public int speed; public ToyCar() { name = "Boom Boom"; speed = 0; } public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", name, speed); } public void SpeedUp(int inc) { speed += inc; } static void Main(string[] args) { ToyCar myToy = new ToyCar(); myToy.PrintState(); Console.WriteLine(); } } }
With an output:
Boom Boom's speed is 0 mph.
In general, classes define additional constructors besides the default. We provide the object user with a simple and consistent way to initialize the state of an object directly at the time of creation. In the following example, our ToyCar class support a total of three class constructors:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string name; public int speed; public ToyCar() { name = "Boom Boom"; speed = 0; } public ToyCar(string n) { name = n; } public ToyCar(string n, int s) { name = n; speed = s; } public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", name, speed); } public void SpeedUp(int inc) { speed += inc; } static void Main(string[] args) { ToyCar myToy = new ToyCar(); myToy.PrintState(); ToyCar myToy2 = new ToyCar("Boong Boong 2"); myToy2.PrintState(); ToyCar myToy3 = new ToyCar("Boong Boong 3", 25); myToy3.PrintState(); Console.WriteLine(); } } }
Output of the example is:
Boom Boom's speed is 0 mph. Boong Boong 2's speed is 0 mph. Boong Boong 3's speed is 25 mph.
What makes one constructor different from another is the number of and type of constructor arguments. So, the ToyCar has overloaded the constructor to provide a number of ways to create the object at the time of declaration.
All classes are provided with a default constructor. However, as soon as we define a custom constructor, the default constructor is silently removed from the class and is no longer available. In other words, if we don't define a custom constructor, the C# compiler grants us a default in order to allow the object user to allocate an instance of our type with field data set to the correct default values. However, when we define a unique constructor, the compiler assumes we have taken all things into our own hands. We get compiler error as in the following example:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string name; public int speed; /* public ToyCar() { name = "Boom Boom"; speed = 0; } */ public ToyCar(string n) { name = n; } public ToyCar(string n, int s) { name = n; speed = s; } public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", name, speed); } public void SpeedUp(int inc) { speed += inc; } static void Main(string[] args) { ToyCar myToy = new ToyCar(); myToy.PrintState(); ToyCar myToy2 = new ToyCar("Boong Boong 2"); myToy2.PrintState(); ToyCar myToy3 = new ToyCar("Boong Boong 3", 25); myToy3.PrintState(); Console.WriteLine(); } } }
We get the following compile time error:
Error: 'SimpleClasses.ToyCar' does not contain a constructor that takes 0 arguments
So, if we want to allow the object user to create an instance of our type with the default constructor, as well as our custom constructor, we must explicitly redefine the default.
In a vast majority of cases, the implementation of the default constructor of a class is intentionally empty, as all we require is the ability to create an object with default values.
C# supports this keyword which provides access to the current class instance. One of uses of this keyword is to resolve scope ambiguity, which can arise when an incoming parameter is named identically to a data field of the type as shown in the following example:
class ToyCar { public string name; public int speed; public void SetName(string name) { name = name; } ... }
The code will compile just fine, however, as shown in the following example, the value of the name filed is an empty string.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string name; public int speed; public ToyCar() { } public void SetName(string name) { name = name; } public ToyCar(int s) { speed = s; } public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", name, speed); } static void Main(string[] args) { ToyCar myToy = new ToyCar(100); myToy.SetName("Boong Boong"); myToy.PrintState(); Console.WriteLine(); } } }
Output shows the name field is empty.
's speed is 100 mph.
The problem is that the implementation of SetName() is assigning the incoming parameter back to itself given that the compiler assumes name is referring to the variable currently in the method scope rather than the name field at the class scope. To inform the compiler that we want to set the current object's name field to the incoming name parameter, simply use this to resolve the ambiguity:
public void SetName(string name) { this.name = name; }
If there is no ambiguity, we are not required to use this keyword when a class wants to access its own data or members. For example, if we rename the string data member to nameToy, the use of this is optional as there is no longer a scope ambiguity:
class ToyCar { public string nameToy; public int speed; public ToyCar() { } public void SetName(string name) { nameToy = name; this.nameToy = name; } ... }
Another way of using this keyword is to design a class using constructor chaining. This is helpful when we have a class that defines multiple constructors. Given the fact that constructors often validate the incoming arguments to enforce various rules, it can be common to find redundant validation logic within a class's constructor set.
class ToyCar { public string nameToy; public int speed; public ToyCar() { } public void SetName(string name) { this.nameToy = name; } public ToyCar(int s) { if(s < 10) speed = 10; speed = s; } public ToyCar(string n, int s) { if (s < 10) speed = 10; speed = s; nameToy = n; } ... }
In the example code above, each constructor is ensuring that the speed level is never less than 10. While this is all well, we have redundant code in tow constructors and we are required to update code in multiple locations if our rules change.
One way to improve the situation is to define a method in the ToyCar class that will validate the incoming argument(s). If we were to do so, each constructor could make a call to this method before making the field assignment(s). While this approach does allow us to isolate the code we need to update when rules change, we are not dealing with the following redundancy:
class ToyCar { public string nameToy; public int speed; public ToyCar() { } public void SetName(string name) { this.nameToy = name; } public ToyCar(int s) { SetSpeed(s); } public ToyCar(string n, int s) { SetSpeed(s);; nameToy = n; } public void SetSpeed(int s) { if (s < 10) speed = 10; speed = s; } ... }
Another approach is to designate the constructor that takes the greatest number of argumentsas the master constructor and have its implementation perform the required validation logic. The remaining constructors can use this keyword to forward the incoming arguments to the master constructor and provide any additional parameters as necessary. In this way, we only need to worry about maintaining a single constructor for the entire class while the remaining constructors are empty.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string nameToy; public int speed; public ToyCar() { } public ToyCar(int s) : this("", s) {} public ToyCar(string n) : this(n, 0) { } public ToyCar(string n, int s) { if (s < 10) speed = 10; speed = s; nameToy = n; } public void SetName(string name) { this.nameToy = name; } public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", nameToy, speed); } static void Main(string[] args) { ToyCar myToy = new ToyCar(100); myToy.SetName("Boom Boom Boom"); myToy.PrintState(); Console.WriteLine(); } } }
Output is:
Boom Boom Boom's speed is 100 mph.
When chaining constructors, note how this keyword is dangling off the constructor's declaration with a colon outside the scope of the constructor itself.
Once a constructor passes arguments to the designated master constructor, the constructor invoked originally by the called will finish executing any remaining code statements:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleClasses { class ToyCar { public string nameToy; public int speed; public ToyCar() { Console.WriteLine("Default ctor"); } public ToyCar(int s) : this("", s) { Console.WriteLine("ctor taking an int"); } public ToyCar(string n) : this(n, 0) { Console.WriteLine("ctor taking a string"); } public ToyCar(string n, int s) { Console.WriteLine("Master ctor"); if (s < 10) speed = 10; speed = s; nameToy = n; } public void SetName(string name) { this.nameToy = name; } public void PrintState() { Console.WriteLine("{0}'s speed is {1} mph.", nameToy, speed); } static void Main(string[] args) { ToyCar myToy = new ToyCar(100); myToy.SetName("Boom Boom Boom"); myToy.PrintState(); Console.WriteLine(); } } }
Output is:
Master ctor ctor taking an int Boom Boom Boom's speed is 100 mph.
Here is the summary of the constructor flow:
- We create our object by invoking the constructor requiring a single int.
- This constructor forwards the supplied data to the master constructor and provides any additional startup arguments not specified by the caller.
- The master constructor assigns the incoming data to the object's field data.
- Control is returned to the constructor originally called, and execute any remaining code statements.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization