Next, we will define some fields in the class to store information about a person.
Inside the Person
class, write the following code. At this point, we have decided that a person is composed of a name and a date of birth. We have encapsulated these two values inside the person. We have also made the fields public so that they are visible outside the class itself:
public class Person : object
{
// fields
public string Name;
public DateTime DateOfBirth;
}
In Visual Studio 2017, you might want to click, hold, and drag the tabs for one of your open files to arrange them so that you can see both Person.cs
and Program.cs
at the same time, as shown in the following screenshot:
In Visual Studio Code, you can click on the Split Editor button or press Cmd + and then close one copy of the duplicated file editor so that you have two files open side by side, as shown in the following screenshot:
Note that, like we did with the class, we applied the public
keyword to these fields. If we hadn't, then they would be private
to the class, which means they are accessible only inside the class.
There are four access modifier keywords that you can apply to a class member, such as a field or method. Part of encapsulation is choosing how visible the members are:
Access Modifier |
Description |
|
Member is accessible inside the type only. This is the default. |
|
Member is accessible inside the type and any type in the same assembly. |
|
Member is accessible inside the type and any type that inherits from the type. |
|
Member is accessible inside the type, any type in the same assembly, and any type that inherits from the type. |
|
Member is accessible everywhere. |
Inside the Main
method, change the code to look like this:
var p1 = new Person();
p1.Name = "Bob Smith";
p1.DateOfBirth = new DateTime(1965, 12, 22);
WriteLine($"{p1.Name} was born on {p1.DateOfBirth:dddd, d MMMM yyyy}");
Run the application and view the output:
Bob Smith was born on Wednesday, 22 December 1965
You can also initialize fields using a short-hand object initializer syntax using curly braces.
Add the following code underneath the existing code to create another new person. Notice the different format code for the date of birth when writing to the console:
var p2 = new Person { Name = "Alice Jones", DateOfBirth = new DateTime(1998, 3, 17) }; WriteLine($"{p2.Name} was born on {p2.DateOfBirth:d MMM yy}");
Run the application and view the output:
Bob Smith was born on Wednesday, 22 December 1965 Alice Jones was born on 17 Mar 98
Sometimes, a value needs to be one of a limited list of options. For example, a person may have a favorite ancient world wonder. Sometimes, a value needs to be combination of a limited list of options. For example, a person may have a bucket list of ancient world wonders they want to visit. We can store this data using an enum
type.
An enum
is a very efficient way of storing one or more choices because, internally, it uses int
values in combination with a lookup table of string descriptions.
In Visual Studio 2017, add a new class to the Ch06_PacktLibrary
project named WondersOfTheAncientWorld
by pressing Shift + Alt +
C or going to Project | Add Class....
In Visual Studio Code, add a new class to the project by selecting Ch06_PacktLibrary
, clicking the New File button in the mini toolbar, and entering the name WondersOfTheAncientWorld.cs
, as shown in the following screenshot:
Modify the WondersOfTheAncientWorld.cs
class file to make it look like this:
namespace Packt.CS7 { public enum WondersOfTheAncientWorld { GreatPyramidOfGiza, HangingGardensOfBabylon, StatueOfZeusAtOlympia, TempleOfArtemisAtEphesus, MausoleumAtHalicarnassus, ColossusOfRhodes, LighthouseOfAlexandria } }
In the Person
class, add the following statement to your list of fields:
public WondersOfTheAncientWorld FavouriteAncientWonder;
Back in the Main
method of Program.cs
, add the following statements:
p1.FavouriteAncientWonder = WondersOfTheAncientWorld.StatueOfZeusAtOlympia; WriteLine($"{p1.Name}'s favourite wonder is {p1.FavouriteAncientWonder}");
Run the application and view the additional output:
Bob Smith's favourite wonder is StatueOfZeusAtOlympia
For the bucket list, we could create a collection of instances of the enum
, but there is a better way. We can combine multiple choices into a single value using flags.
Modify the enum
to look as shown in the following code. Note that I have used the left shift operator (<<
) to set individual bits within the flag. I could also have set the values to 1, 2, 4, 8, 16, 32, and so on:
namespace Packt.CS7 { [System.Flags] public enum WondersOfTheAncientWorld : byte { None = 0, GreatPyramidOfGiza = 1, HangingGardensOfBabylon = 1 << 1, StatueOfZeusAtOlympia = 1 << 2, TempleOfArtemisAtEphesus = 1 << 3, MausoleumAtHalicarnassus = 1 << 4, ColossusOfRhodes = 1 << 5, LighthouseOfAlexandria = 1 << 6 } }
We are assigning explicit values for each choice that would not overlap when looking at the bits stored in memory. We must also mark the enum
with the System.Flags
attribute. Normally, an enum
uses an int
variable internally, but since we don't need values that big, we can make it more efficient by telling it to use a byte
variable.
If we want to indicate that our bucket list includes the Hanging Gardens and the Mausoleum at Halicarnassus, then we would want the 16 and 2 bits set to 1
. In other words, we would store the value 18
:
64 |
32 |
16 |
8 |
4 |
2 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
0 |
In the Person
class, add the following statement to your list of fields:
public WondersOfTheAncientWorld BucketList;
Back in the Main
method of Ch06_PeopleApp
, add the following statements to set the bucket list using the |
operator (logical OR) to combine enum
values. We could also set the value using the number 18
cast into the enum
type as in the comment:
p1.BucketList = WondersOfTheAncientWorld.HangingGardensOfBabylon | WondersOfTheAncientWorld.MausoleumAtHalicarnassus; // p1.BucketList = (WondersOfTheAncientWorld)18; WriteLine($"{p1.Name}'s bucket list is {p1.BucketList}");
Run the application and view the additional output:
Bob Smith's bucket list is HangingGardensOfBabylon,
MausoleumAtHalicarnassus
Let's add a field to store a person's children. This is an example of aggregation because children are instances of a class that is related to the current person, but are not part of the person itself.
We will use a generic List<T>
collection type, so we need to import the System.Collections.Generic
namespace:
using System.Collections.Generic;
Then we can declare a new field in the Person
class:
public List<Person> Children = new List<Person>();
Notice that we need to ensure the collection is initialized to a new instance of a collection before we can add items to the collection.
In the Main
method, add the following code:
p1.Children.Add(new Person()); p1.Children.Add(new Person()); WriteLine($"{p1.Name} has {p1.Children.Count} children.");
Run the application and view the output:
Bob Smith has 2 children.
The fields that we have created so far have all been instance members, meaning that a copy of each field exists for each instance of the class that is created.
Sometimes, you want to define a field that only has one copy that is shared across all instances. These are called static members.
In the Ch06_PacktLibrary
project, add a new class named BankAccount
. Modify the class as shown in the following code:
namespace Packt.CS7 { public class BankAccount { public string AccountName; public decimal Balance; public static decimal InterestRate; } }
In Program.cs
and its Main
method, add the following code where we will set the shared interest rate and then create two instances of the BankAccount
type:
BankAccount.InterestRate = 0.012M; var ba1 = new BankAccount(); ba1.AccountName = "Mrs. Jones"; ba1.Balance = 2400; WriteLine($"{ba1.AccountName} earned {ba1.Balance * BankAccount.InterestRate:C} interest."); var ba2 = new BankAccount(); ba2.AccountName = "Ms. Gerrier"; ba2.Balance = 98; WriteLine($"{ba2.AccountName} earned {ba2.Balance * BankAccount.InterestRate:C} interest.");
Run the application and view the additional output:
Mrs. Jones earned £28.80 interest. Ms. Gerrier earned £1.18 interest.
:C
is a format code that tells .NET to use the currency format for the numbers. In Chapter 4, Using .NET Standard Types, you learned how to control the culture that determines the currency symbol.
If the value of a field will never ever change, you can use the const
keyword and assign the value at compile time.
Inside the Person
class, add the following code:
// constants public const string Species = "Homo Sapien";
Inside the Main
method, change the code to look like this. Note that, to read a constant field, you must write the name of the class, not the name of an instance of the class:
WriteLine($"{p1.Name} is a {Person.Species}");
Run the application and view the additional output:
Bob Smith is a Homo Sapien
Examples of const
fields in Microsoft types include System.Int32.MaxValue
and System.Math.PI
because neither value will ever change, as you can see in the following screenshot:
Good Practice
Constants should be avoided for two important reasons:
The value must be known at compile time, and it must be expressible as a literal string, Boolean, or number value.
Every reference to the const
field is replaced with the literal value at compile time, which will, therefore, not be reflected if the value changes in a future version.
A better choice for fields that should not change is to mark them as read-only.
Inside the Person
class, write the following code:
// read-only fields public readonly string HomePlanet = "Earth";
Inside the Main
method, add the following code statement. Notice that, to get a read-only field, you must write the name of an instance of the class, not the type name, unlike const
:
WriteLine($"{p1.Name} was born on {p1.HomePlanet}");
Run the application and view the output:
Bob Smith was born on Earth
Good Practice
Use read-only fields over const
fields for two important reasons:
The value can be calculated or loaded at runtime and can be expressed using any executable statement. So, a read-only field can be set using a constructor.
Every reference to the field is a live reference, so any future changes will be correctly reflected by calling code.
Fields often need to be initialized at runtime. You do this in a constructor that will be called when you make an instance of the class using the new
keyword. Constructors execute before any fields are set by the code that is using the type.
Inside the Person
class, add the following highlighted code after the existing read-only HomePlanet
field:
// read-only fields public readonly string HomePlanet = "Earth"; public readonly DateTime Instantiated; // constructors public Person() { // set default values for fields // including read-only fields Name = "Unknown"; Instantiated = DateTime.Now; }
Inside the Main
method, add the following code:
var p3 = new Person(); WriteLine($"{p3.Name} was instantiated at {p3.Instantiated:hh:mm:ss} on {p3.Instantiated:dddd, d MMMM yyyy}");
Run the application and view the output:
Unknown was instantiated at 11:58:12 on Sunday, 12 March 2017
You can have multiple constructors in a type.
Inside the Person
class, add the following code:
public Person(string initialName) { Name = initialName; Instantiated = DateTime.Now; }
Inside the Main
method, add the following code:
var p4 = new Person("Aziz"); WriteLine($"{p4.Name} was instantiated at {p4.Instantiated:hh:mm:ss} on {p4.Instantiated:dddd, d MMMM yyyy}");
Run the application and view the output:
Aziz was instantiated at 11:59:25 on Sunday, 4 June 2017
Constructors are a special category of method. Let's look at methods in more detail.
18.116.27.178