Time
Class Case Study: Overloaded ConstructorsNext, we demonstrate a class with several overloaded constructors that enable objects of that class to be conveniently initialized in different ways. To overload constructors, simply provide multiple constructor declarations with different signatures.
Time2
with Overloaded ConstructorsBy default, the properties Hour
, Minute
and Second
of class Time1
(Fig. 10.1) are initialized to their default values of 0
—midnight in universal time. Class Time1
doesn’t enable the class’s clients to initialize the time with specific nonzero values, because it does not define such a constructor. Class Time2
(Fig. 10.5) contains overloaded constructors. In this app, one constructor invokes the other, which in turn calls SetTime
to set the private
instance variables hour
, minute
and second
via the class’s Hour
, Minute
and Second
properties, which perform validation. The compiler invokes the appropriate Time2
constructor by matching the number and types of the arguments specified in the constructor call with the number and types of the parameters specified in each constructor declaration.
Time2
’s Three-Argument ConstructorLines 12–15 declare a constructor with three default parameters. We did not define a constructor with an empty parameter list, so for class Time2
the constructor at lines 12–15 is also considered to be the class’s parameterless constructor—you can call the constructor without arguments and the compiler will provide the default values. This constructor also can be called with one argument for the hour, two for the hour and minute, or three for the hour, minute and second. This constructor calls SetTime
to set the time.
A constructor can call methods of its class. Be aware that the instance variables might not yet be initialized, because the constructor is in the process of initializing the object. Using instance variables before they have been initialized properly is a logic error.
Lines 18–19 declare another Time2
constructor that receives a reference to a Time2
object. In this case, the values from the Time2
argument are passed to the three-parameter constructor at lines 12–15 to initialize the hour
, minute
and second
. In this constructor, we use this
in a manner that’s allowed only in the constructor’s header. In line 19,
: this(time.Hour, time.Minute, time.Second) { }
:
this
followed by parentheses containing arguments indicates a call to one of the class’s other constructors—in this case, the Time2
constructor that takes three int
arguments (lines 12–15). Line 19 passes the values of the time
argument’s Hour
, Minute
and Second
properties to initialize the Time2
object being constructed. Any initialization code in the body of the constructor at lines 18–19 would execute after the other constructor is called.
Using this
as in line 19 is called a constructor initializer. It enables a class to reuse initialization code provided by a constructor, rather than defining similar code in another constructor. If we needed to change how objects of class Time2
are initialized, only the constructor at lines 12–15 would need to be modified. Even that constructor might not need modification—it simply calls the SetTime
method to perform the actual initialization, so it’s possible that the changes the class might require would be localized to SetTime
.
Constructor initializers make classes easier to maintain, modify and debug, because the common initialization code can be defined in one constructor and called by others.
Line 19 could have directly accessed instance variables hour
, minute
and second
of the constructor’s time
argument with the expressions time.hour
, time.minute
and time.second
—even though they’re declared as private
variables of class Time2
.
When executing a method of a class, if that method has a reference to another object of the same class (typically received via a parameter), the method can access all of that other object’s data and methods (including those that are private
).
SetTime
Method and the Hour
, Minute
and Second
PropertiesMethod SetTime
(lines 23–28) invokes the set
accessors of the properties Hour
(lines 31–47), Minute
(lines 50–66) and Second
(lines 69–85), which ensure that the hour is in the range 0 to 23 and that the values for the minute and second are each in the range 0 to 59. If a value is out of range, each set
accessor throws an ArgumentOutOfRangeException
(lines 41–42, 60–61 and 79–80). In this example, we use the exception class’s overloaded constructor that receives three arguments:
the string
name of the item that was out of range
the value that was supplied for that item and
a string
error message.
It’s common to include in an exception’s error message a variable’s or property’s identifier. This information can help a client-code programmer understand the context in which the exception occurred. Prior to C# 6, you had to hard code these identifiers into your error-message string
s. As of C# 6, you can instead use the nameof
operator (lines 41–42, 60–61 and 79–80), which returns a string
representation of the identifier enclosed in parentheses. For example, the expression
nameof(value)
in line 41 returns the string
"value"
and the expression
nameof
(Hour)
in line 42 returns the string
"Hour"
.
When you need to include an identifier in a string
literal, use the nameof
operator rather than hard coding the identifier’s name in the string
. If you right click an identifier in Visual Studio then use the Rename… option to change the identifier’s name throughout your code, the string
that nameof
returns will be updated automatically with the identifier’s new name.
Time2
’s Methods, Properties and ConstructorsTime2
’s properties are accessed throughout the class’s body—SetTime
assigns values to Hour
, Minute
and Second
in lines 25–27, and ToUniversalString
and ToString
use properties Hour
, Minute
and Second
in line 89 and lines 93–94, respectively. These methods could access the class’s private
data directly. However, consider changing the time’s representation from three int
values (requiring 12 bytes of memory) to one int
value representing the total number of elapsed seconds since midnight (requiring only four bytes of memory). If we make this change, only code that accesses the private
data directly would need to change—for class Time2
, the bodies of properties Hour
, Minute
and Second
. There would be no need to modify SetTime
, ToUniversalString
or ToString
, because they access the private
data indirectly through Hour
, Minute
and Second
. Designing a class in this manner reduces the likelihood of programming errors when altering the class’s implementation.
Similarly, each constructor could include a copy of the appropriate statements from method SetTime
. Doing so may be slightly more efficient, because the extra constructor call and the call to SetTime
are eliminated. However, duplicating statements in multiple methods or constructors makes changing the class’s internal data representation more difficult and error-prone. Having one constructor call the other or even call SetTime
directly allows any changes to SetTime
’s implementation to be made only once.
When implementing a method of a class, using the class’s properties to access the class’s private
data simplifies code maintenance and reduces the likelihood of errors.
Time2
’s Overloaded ConstructorsClass Time2Test
(Fig. 10.6) creates six Time2
objects (lines 9–13 and 41) to invoke the overloaded Time2
constructors.
Lines 9–13 demonstrate passing arguments to the Time2
constructors. C# invokes the appropriate overloaded constructor by matching the number and types of the arguments in the constructor call with the number and types of the parameters in each constructor declaration. Lines 9–12 each invoke the constructor at lines 12–15 of Fig. 10.5:
Line 9 of Fig. 10.6 invokes the constructor with no arguments—the compiler supplies the default value 0
for each of the three parameters.
Line 10 invokes the constructor with one argument that represents the hour—the compiler supplies the default value 0
for the minute and second.
Line 11 invokes the constructor with two arguments that represent the hour and minute—the compiler supplies the default value 0
for the second.
Line 12 invoke the constructor with values for the hour, minute and second.
Line 13 invokes the constructor at lines 18–19 of Fig. 10.5. Lines 15–36 (Fig. 10.6) display the string
representation of each initialized Time2
object to confirm that each was initialized properly.
Line 41 attempts to initialize t6
by creating a new Time2
object and passing three invalid values to the constructor. When the constructor attempts to use the invalid hour value to initialize the Hour
property, an ArgumentOutOfRangeException
occurs. We catch this exception at line 43 and display its Message
property, which results in the last three lines of the output in Fig. 10.6. Because we used the three-argument Argument-OutOfRangeException
constructor when the exception object was created, the exception’s Message
property also includes the information about the out-of-range value.