One alternative to formatting the names in the WriteLine() method call within Main() is to provide a method in the Employee class that takes care of the formatting. Changing the functionality to be within the Employee class rather than a member of Program is consistent with the encapsulation of a class. Why not group the methods relating to the employee’s full name with the class that contains the data that forms the name? Listing 6.7 demonstrates the creation of such a method.
There is nothing particularly special about this method compared to what you learned in Chapter 5, except that now the GetName() method accesses fields on the object instead of just local variables. In addition, the method declaration is not marked with static. As you will see later in this chapter, static methods cannot directly access instance fields within a class. Instead, it is necessary to obtain an instance of the class to call any instance member, whether a method or a field.
Given the addition of the GetName() method, you can update Program.Main() to use the method, as shown in Listing 6.8 and Output 6.2.
You can obtain the reference to a class from within instance members that belong to the class. To indicate explicitly that the field or method accessed is an instance member of the containing class in C#, you use the keyword this. Use of this is implicit when calling any instance member, and it returns an instance of the object itself.
For example, consider the SetName() method shown in Listing 6.9.
This example uses the keyword this to indicate that the fields FirstName and LastName are instance members of the class.
Although the this keyword can prefix any and all references to local class members, the general guideline is not to clutter code when there is no additional value. Therefore, you should avoid using the this keyword unless it is required. Listing 6.12 (later in this chapter) is an example of one of the few circumstances when such a requirement exists. Listing 6.9 and Listing 6.10, however, are not good examples. In Listing 6.9, this can be dropped entirely without changing the meaning of the code. And in Listing 6.10(presented next), by changing the naming convention for fields and following the naming convention for parameters, we can avoid any ambiguity between local variables and fields.
In Listing 6.9 and Listing 6.10, the this keyword is not used in the GetName() method—it is optional. However, if local variables or parameters exist with the same name as the field (see the SetName() method in Listing 6.10), omitting this would result in accessing the local variable/parameter when the intention was to access the field; given this scenario, use of this is required.
You also can use the keyword this to access a class’s methods explicitly. For example, this.GetName() is allowed within the SetName() method, permitting you to print out the newly assigned name (see Listing 6.11 and Output 6.3).
Sometimes it may be necessary to use this to pass a reference to the currently executing object. Consider the Save() method in Listing 6.12.
The Save() method in Listing 6.12 calls a method on the DataStorage class, called Store(). The Store() method, however, needs to be passed the Employee object, which needs to be persisted. This is done using the keyword this, which passes the instance of the Employee object on which Save() was called.
The actual implementation of the Store() method inside DataStorage involves classes within the System.IO namespace, as shown in Listing 6.13. Inside Store(), you begin by instantiating a FileStream object that you associate with a file corresponding to the employee’s full name. The FileMode.Create parameter indicates that you want a new file to be created if there isn’t already one with the <firstname><lastname>.dat name; if the file exists already, it will be overwritten. Next, you create a StreamWriter class, which is responsible for writing text into the FileStream. You write the data using WriteLine() methods, just as though writing to the console.
Once the write operations are completed, both the FileStream and the StreamWriter need to be closed so that they are not left open indefinitely while waiting for the garbage collector to run. Listing 6.13 does not include any error handling, so if an exception is thrown, neither Close() method will be called.
The load process is similar (see Listing 6.14 with Output 6.4).
The reverse of the save process appears in Listing 6.14, which uses a StreamReader rather than a StreamWriter. Again, Close() needs to be called on both FileStream and StreamReader once the data has been read.
Output 6.4 does not show any salary after Inigo Montoya: because Salary was not set to Enough to survive on by a call to IncreaseSalary() until after the call to Save().
Notice in Main() that we can call Save() from an instance of an employee, but to load a new employee we call DataStorage.Load(). To load an employee, we generally don’t already have an employee instance to load into, so an instance method on Employee would be less than ideal. An alternative to calling Load on DataStorage would be to add a static Load() method (see the section “Static Members” later in this chapter) to Employee so that it would be possible to call Employee.Load() (using the Employee class, not an instance of Employee).