C# 11 Dates and Times

It is a rare application that can be developed without in some way needing to work with dates and times. Recognizing this, the Microsoft engineers responsible for C# gave us the DateTime object. This chapter will look at using this object to work with dates and times in C# based applications. Topics covered include adding and subtracting time, getting the system date and time, and formatting and extracting elements of dates and times in C#.

Creating a C# DateTime object

The first step in using the DateTime object when working with dates and times in C# is to create an object instance. You do this using the new keyword passing through year, month, and day values. For example, to create a DateTime object preset to April 9, 2021, you would write the following code:

DateTime meetingAppt = new DateTime(2023, 4, 9);
System.Console.WriteLine (meetingAppt.ToString());

Output:

04/09/2023 00:00:00

In the above example, after setting the date, we use the ToString() method of the DateTime object to output the current date and time value as a string.

Note: If a time is not specified along with the date, the DateTime class constructor will set the time to 12:00 am.

Time values are specified by passing through hours, minutes, and seconds values to the constructor. The following code, for example, sets the time property of a DateTime object to 14:30:00 using the same date as before:

DateTime meetingAppt = new DateTime(2023, 4, 9, 14, 30, 0);
System.Console.WriteLine (meetingAppt.ToString());

Output:

04/09/2023 14:30:00

Getting the current system time and date

The system date and time of the computer on which the C# code is executing may be accessed using the Today and Now static properties of the DateTime class. The Today property will return the current system date (and the time set to 12:00 AM), while the Now property returns the current date and time. For example, the following code demonstrates these properties (keeping in mind that the server on which the code is executing may not be in your timezone):

System.Console.WriteLine(DateTime.Today.ToString());
System.Console.WriteLine(DateTime.Now.ToString());

Output:

03/07/2023 00:00:00
03/07/2023 19:52:30

Adding or subtracting from dates and times

The C# DateTime object provides several methods for adding or subtracting dates and times from a DateTime object instance. These methods are outlined in the following table:

MethodDescription
AddAdds/Subtracts the value of the specified TimeSpan object instance.
AddDaysAdds/Subtracts the specified number of days.
AddHoursAdds/Subtracts the specified number of hours.
AddMillisecondsAdds/Subtracts the specified number of Milliseconds.
AddMinutesAdds/Subtracts the specified number of minutes.
AddMonthsAdds/Subtracts the specified number of months.
AddSecondsAdds/Subtracts the specified number of seconds.
AddYearsAdds/Subtracts the specified number of years.
Table 1-1

A vital issue to understand is that these methods do not change the value of the DateTime object on which the method is called but instead return a new DateTime object primed with the modified date and time. For example, add five days to our example as follows:

DateTime meetingAppt = new DateTime(2023, 4, 9, 14, 30, 0);
DateTime newAppt = meetingAppt.AddDays(5);
System.Console.WriteLine (newAppt.ToString());

Output:

04/14/2023 14:30:00

To subtract from a date and time, pass through a negative value to the appropriate method. For example, we can subtract ten months from our example object as follows:

DateTime meetingAppt = new DateTime(2023, 4, 9, 14, 30, 0);
DateTime newAppt = meetingAppt.AddMonths(-10);
System.Console.WriteLine (newAppt.ToString());

Output:

06/09/2022 14:30:00

Retrieving parts of a date and time

Dates and times comprise distinct and separate values, namely the day, month, year, hours, minutes, seconds, and milliseconds. The C# DateTime object stores each value as a separate property within the object, allowing each to be accessed individually. For example, the following code sample extracts each value and displays it in the console:

DateTime meetingAppt = new DateTime(2023, 4, 9, 14, 30, 0);

System.Console.WriteLine(meetingAppt.Day);
System.Console.WriteLine(meetingAppt.Month);
System.Console.WriteLine(meetingAppt.Year);
System.Console.WriteLine(meetingAppt.Hour);
System.Console.WriteLine(meetingAppt.Minute);
System.Console.WriteLine(meetingAppt.Second);
System.Console.WriteLine(meetingAppt.Millisecond);

Output:

9
4
2023
14
30
0
0

C# 11 String Formatting

In addition to the wide selection of string manipulation functions outlined in the C# Strings lesson, the string class also provides the  String.Format() method.

The primary purpose of the C#  String.Format() method is to provide a mechanism for inserting string, numerical, or boolean values into a string with additional formatting control.

The Syntax of the String.Format() Method

The general syntax of the String.Format() method is as follows:

String.Format("format string", arg1, arg2, .... ); 

The format string is the string into which the values will be placed. Within this string are placeholders that indicate the location of each value within the string. Placeholders take the form of braces {} surrounding a number indicating the corresponding argument to be substituted for the placeholder. Following on from the format string is a comma-separated list of arguments. There must be an argument for each of the placeholders.

A simple string formatting example

The following code example demonstrates a straightforward use of the  String.Format() method:

string newString;

newString = String.Format("There are {0} cats in my {1} and no {2}", 2, "house", "dogs");

System.Console.WriteLine (newString);

Output:

There are 2 cats in my house and no dogs

Let’s quickly review the String.Format() method call in the above example. The format string contains three placeholders indicated by {0}, {1}, and {2}. Following the format string are the arguments to be used in each placeholder. So, for example, {0} is replaced by the first argument (the number 2), the {1} by the second argument (the string “house”), and so on.

If you cast your mind back to the C# 11 Character and String Data Types chapter, you will probably wonder how this compares to the string interpolation technique we have used countless times throughout the chapter. As we will see below, C# string formatting becomes more useful when combined with format controls.

Using format controls

So far, we have only substituted arguments for placeholders, but we have not changed the format of the arguments before they are displayed. This essentially instructs the Format() method to use the default formatting for each argument type when displaying the string. Perhaps the most powerful aspect of the Format() method is the ability to use format controls within the placeholders to control the output format.

Format controls appear inside the braces ({}) of the placeholders. The format of a placeholder with a format control is as follows:

{n:controlx} 

Where n is the placeholder number and control is the special format control sequence to be applied to the argument. The x optional number further formats the output for certain control types.

A simple format control example

The following example uses the X format control, which is used to display a number using hexadecimal format:

var newString = String.Format("The number {0} in Hexadecimal is {0:X}.", 432);

System.Console.WriteLine (newString);

Output:

The number 432 in Hexadecimal is 1B0.

The above example displays argument 0 (432) in two formats. The first is the default decimal format, while the second uses the X format control to display the argument as a hexadecimal number.

The String.Format() method provides a wide range of format controls which we will explore below.

C# String.Format() format controls

The following table lists format controls supported by the C#  String.Format() method together with examples of each control:

ControlTypeDescriptionExample
CCurrencyDisplays number prefixed with the currency symbol appropriate to the current locale{0:C} of 432.00 outputs $432.00
DDecimalDisplays numbers in decimal form with optional padding{0:D4} of 432 outputs 00432
EExponentialDisplays number in scientific form with optional value for the fractional part{0:E5} of 432.32 outputs 4.32320E+002
FFixedDisplays the number, including the specified number of decimal digits{0:F3} of 432.324343 outputs 432.324
NNumberConverts a number to a human-friendly format by inserting commas and rounding to the nearest 100th{0:N} of 123432.324343 outputs 123,432.32
XHexadecimalConverts a number to hexadecimal{0:X} of 432 outputs 1B0
0:0…Zero PaddingAdds zeros to pad argument{0:0000.00} of 43.1 outputs 0043.10
0:0#…Space PaddingAdds spaces to pad argument{0:####.##} of 43.1 outputs 43.1
%PercentageMultiplies the argument by 100 and appends a percentage sign{0:00.00%} of .432 outputs 43.20%

Before moving on to the next lesson, run the following code to see some of these formats controls in action:

var newString = String.Format("The number {0} fixed to 2 decimal places is {0:F2}.", 109.78799);

System.Console.WriteLine (newString);

newString = String.Format("The number {0} using human friendly formatting is {0:N}.", 1023429.78799);
System.Console.WriteLine (newString);

newString = String.Format("The number {0} using exponential formatting is {0:E6}.", 32423.24232);
System.Console.WriteLine (newString);

newString = String.Format("The number {0} as a percentage to 3 decinal places is {0:00.000%}.", 0.24232);
System.Console.WriteLine (newString);

Output:

The number 109.78799 fixed to 2 decimal places is 109.79.
The number 1023429.78799 using human friendly formatting is 1,023,429.79.
The number 32423.24232 using exponential formatting is 3.242324E+004.
The number 0.24232 as a percentage to 3 decinal places is 24.232%.

C# 11 String Examples

Strings are collections of characters that are grouped together to form words or sentences. If it wasn’t for humans, computers would probably never have anything to do with strings. The fact is, however, that one of the primary jobs of a computer is to accept data from and present data to humans. For this reason, it is highly likely that any C# program is going to involve a considerable amount of code specifically designed to work with data in the form of strings.

Creating strings in C#

Strings consist of sequences of characters contained in a string object. A string object may be created using a number of different mechanisms.

A string may be declared but not initialized as follows:

string myString;

Alternatively, you can assign a literal value to a string in C# using the assignment operator:

string myString = "Hello World";

It is, of course, also possible to declare a string using implicit typing:

var myString = "Hello World";

Similarly, a new string may be created using the new keyword and passing through the literal value to the constructor:

string myString = new String("Hello World");

String literals are placed within double quotes (as shown above). If the string itself contains double quotes, the escape character (\) should precede the double-quote characters:

System.Console.WriteLine ("He shouted \"Can you hear me?\"");

Output:

He shouted "Can you hear me?"

You can also instruct C# to treat all the characters in a string verbatim by prefixing the string with the @ character. When using @ notation, everything between the double quotes is treated as a raw string, regardless of whether new lines, carriage returns, backslashes, etc., are present in the text. For example:

System.Console.WriteLine (@"You can put a backslash \ here
and a new line
and tabs			work too. 
You can also put in sequences that would normally be seen as escape sequences like \n and \t.");

Output:

You can put a backslash \ here
and a new line
and tabs			work too. 
You can also put in sequences that would normally be seen as escape sequences like \n and \t.

If you are familiar with the heredoc function of other programming languages, you will quickly notice that this is essentially the C# equivalent.

Obtaining the length of a C# string

If you need to identify the length of a C# string, you can do so by accessing the Length property of the string object:

string myString = "Hello World";
System.Console.WriteLine ($"myString length = {myString.Length}");

Output:

myString length = 11

Treating strings as arrays

It is possible to access individual characters in a string by treating the string as an array (arrays were covered in the C# Arrays lesson).

By specifying the index value using subscripting syntax ([]), you can access individual characters in a string (keeping in mind that the first character is at index position 0):

string myString = "Hello World";

System.Console.WriteLine(myString[0]);
System.Console.WriteLine(myString[2]);
System.Console.WriteLine(myString[4]);

Output:

H
l
o

It is important to note that strings are immutable (in other words, while an entirely new string literal may be assigned to the variable, you cannot change the individual characters in a string). To experience this limitation, try running the following code:

string myString = "Hello World";

myString[5] = '-';

When attempting to run the above code, you will have received an error from the compiler stating the following:

Property or indexer `string.this[int]' cannot be assigned to (it is read-only)

It is also possible to convert a string to an array of characters by making a call to the string object’s ToCharArray method as follows:

string myString = "Hello World";

char[] charArray = myString.ToCharArray();

foreach (char c in charArray) {
    System.Console.WriteLine(c);
}

Output:

H
e
l
l
o
 
W
o
r
l
d

String character iteration

Given that we can treat each character in an array as an array element, it should not surprise you to learn that we can iterate through the characters of a string using a foreach loop as follows:

string myString = "Hello World";

foreach (char c in myString) {
    System.Console.WriteLine(c);
}

Output:

G
o
o
d
b
y
e
 
W
o
r
l
d

Concatenating strings

Strings may be concatenated (i.e., joined together) simply by adding them together using the addition operator (+).

We can, therefore, combine two strings as follows:

string myString = "Hello World.";

System.Console.WriteLine (myString + " How are you?");

Output:

Hello World. How are you?

Alternatively, you can concatenate strings using the Concat() instance method of the String class. This method takes two strings to be joined as arguments and returns a new string containing the union of the two strings:

string myString1 = "If at first you don't succeed, ";
string myString2 = "try, try again.";

string myString3 = String.Concat(myString1, myString2);

System.Console.WriteLine(myString3);

Output:

If at first you don't succeed, try, try again.

Comparing strings

C# provides several options if you need to compare one string with another. The most common option is to use the equality operator, as demonstrated in the example below:

string myString1 = "Hello World";
string myString2 = "Hello World";

if (myString1 == myString2)
{
	System.Console.WriteLine("The strings match.");
}
else
{
	System.Console.WriteLine("They strings not match.");
}

Output:

The strings match

You can also compare strings in a similar way using the  String.Equals()  instance method. This method takes as arguments to two strings to be compared and returns a Boolean result indicating whether or not the strings match:

string myString1 = "Hello world";
string myString2 = "Hello world";

if (String.Equals(myString1, myString2))
{
	System.Console.WriteLine("The strings match.");
}
else
{
	System.Console.WriteLine("The strings do not match.");
}

Output:

The strings match.

In the above example, we passed both strings through to the Equals() method when performing the comparison. The Equals() method can also be called on a string literal or string object to achieve the same result:

string myString1 = "Hello world";
string myString2 = "Hello world";

if (myString1.Equals(myString2, StringComparison.Ordinal))
{
	System.Console.WriteLine("The strings match.");
}
else
{
	System.Console.WriteLine("The strings do not match.");
}

Output:

The strings match.

So far, all of the comparisons we have explored have performed a case-sensitive comparison. When calling the Equals() method, you can also pass through a comparison type parameter. The type must be taken from the C#  StringComparison  enumeration, which, among values for performing culture-sensitive comparison settings, includes the OrdinalIgnoreCase value for case-insensitive comparisons:

string myString1 = "HELLO WORLD";
string myString2 = "Hello world";

if (String.Equals(myString1, myString2, StringComparison.OrdinalIgnoreCase))
{
	System.Console.WriteLine("The strings match.");
}
else
{
	System.Console.WriteLine("The strings do not match.");
}

Output:

The strings match.

The same approach also works when calling the Equals() method directly on a string literal or object:

var result = "My String".Equals("my string", StringComparison.OrdinalIgnoreCase);

The String.Compare() method provides yet another way to compare strings, though this method has some useful additional features. This method accepts as arguments the two strings to be compared and returns an integer value indicating how the strings relate to each other in relation to sort order. A result of 0 indicates that the strings match. A value of less than 0 indicates that the first string precedes the second string in the sort order. Finally, a result of greater than 0 indicates the second string precedes the first in the sort order:

string myString1 = "Bananas are yellow.";
string myString2 = "Oranges are orange.";

var result = String.Compare(myString1, myString2);

if (result == 0) 
{
	System.Console.WriteLine("Strings match.");
}
else if (result < 0) 
{
	System.Console.WriteLine("myString1 precedes myString2 in sort order.");
}
else if (result > 0) 
{
	System.Console.WriteLine("myString2 precedes myString1 in sort order.");
}

Output:

myString1 precedes myString2 in sort order.

Changing string case

The case of the characters in a string may be changed using the  ToUpper() and ToLower() methods. Both of these methods return a modified string rather than changing the actual string. For example:

string myString = "Hello World";
string newString;

newString = myString.ToUpper();

System.Console.WriteLine(newString);  

newString = myString.ToLower();

System.Console.WriteLine(newString);

Output:

HELLO WORLD hello world

Splitting a string into multiple parts

A string may be separated into multiple parts using the  Split()  method. Split() takes as an argument the character to use as the delimiter to identify the points at which the string is to be split. Returned from the method call is an array containing the individual parts of the string. For example, the following code splits a string up using the comma character as the delimiter. The results are placed in an array called myColors and a foreach loop then reads each item from the array and displays it:

string myString = "Red, Green, Blue, Yellow, Pink, Purple";

string[] myColors = myString.Split(',');

foreach (string color in myColors)
{
	System.Console.WriteLine(color);
}

Output:

Red Green Blue Yellow Pink Purple

As we can see, the Split() method broke the string up as requested, but we have a problem in that the spaces are still present. Fortunately, C# provides a method to handle this.

Trimming and padding strings

Unwanted leading and trailing spaces can be removed from a string using the Trim() method. When called, this method returns a modified version of the string with both leading and trailing spaces removed:

string myString = "    hello      ";

System.Console.WriteLine ("[" + myString + "]");
System.Console.WriteLine ("[" + myString.Trim() + "]");

Output:

[ hello ] [hello]

If you only need to remove leading or trailing spaces, use either the  TrimStart() or TrimEnd() method respectively.

The inverse of the trim methods are the PadLeft() and PadRight() methods. These methods allow leading or trailing characters to be added to a string. The methods take as arguments the total number of characters to which the string is to be padded and the padding character:

string myString = "hello";

string newString;
newString = myString.PadLeft(10, ' ');
newString = newString.PadRight(20, '*');
System.Console.WriteLine ("[" + newString + "]");

Output:

[ hello**********]

String replacement

Parts of a string may be replaced using the Replace() method. This method takes the part of the string to be replaced and the string with which it is to be replaced as arguments and returns a new string reflecting the change. The Replace() method will replace all instances of the string:

string myString = "Hello World";
string newString;

System.Console.WriteLine (myString);
newString = myString.Replace("Hello", "Goodbye");
System.Console.WriteLine (newString);

Output:

Hello World Goodbye World

C# 11 Dictionary Collections

C# dictionaries allow data to be stored and managed as key-value pairs. Dictionaries fulfill a similar purpose to arrays and lists, except each item stored in the dictionary has associated with it a unique key (to be precise, the key is unique to the particular dictionary object) which can be used to reference and access the corresponding value.

Dictionary initialization

A dictionary is a data type explicitly designed to hold multiple values in a single unordered collection. Each item in a dictionary consists of a key and an associated value.

The Dictionary class is contained within the  System.Collections.Generic namespace, which must be imported when working with lists.

An empty dictionary may be created using the following syntax:

Dictionary<key-type, value-type> name =
    new Dictionary<key-type, value-type>();

The following code, for example, creates an empty dictionary using an int for the key and a string for the values:

Dictionary<int, string> movies =
    new Dictionary<int, string>();

Alternatively, you can use implicit typing using the var keyword:

var movies =
    new Dictionary<int, string>();

A new dictionary may be initialized with a collection of values using a collection initializer with the following syntax (note that we are once again using implicit typing):

var name = new Dictionary<key-type, value-type>()
{
    { key1, value1 },
    { key2, value2 },
    { key3, value3 },
.
.
};

The following code creates a new dictionary initialized with four key-value pairs in the form of strings acting as keys for corresponding movie titles:

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

In the above instance, the C# compiler will use implicit typing to decide that both the key and value elements of the dictionary are of string and prevent values or keys of other types from being inserted into the dictionary.

Dictionary item count

A count of the items in a dictionary can be obtained by accessing the dictionary’s Count property:

var movies = new Dictionary<string, string>()
    {
        { "DRA-1212", "The Godfather" },
        { "WAR-4433", "Apocalypse Now" },
        { "COM-5465", "The Terminal" },
        { "CLA-1659", "Casablanca" }
    };

System.Console.WriteLine($"Count = {movies.Count}");

Output:

Count = 4

Dictionary iteration

As with arrays and lists, you can iterate through all entries in a dictionary using foreach looping syntax. The following code, for example, iterates through all of the entries in the movies dictionary, outputting both the key and value for each entry:

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

foreach (var movie in movies) {

    var key = movie.Key;
    var value = movie.Value;

    System.Console.WriteLine($"{key} - {value}");
}

Output:

DRA-1212 - The Godfather
WAR-4433 - Apocalypse Now
COM-5465 - The Terminal
CLA-1659 - Casablanca

As we can see in the above code, each entry within a dictionary has Key and Value properties. In this case, we have used these properties to extract each dictionary entry’s key and movie title value.

Adding and removing dictionary entries

New entries can be added to an existing dictionary by making a call to the Add() method of the instance, passing through the key/value pair:

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

movies.Add("SCI-2323", "Prometheus");

System.Console.WriteLine(movies["SCI-2323"]);

Output:

Prometheus

You can remove an entry from a dictionary by calling the Remove() method, referencing the key matching the entry to be removed:

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

movies.Remove("WAR-4433");

foreach (var movie in movies) {

    var key = movie.Key;
    var value = movie.Value;

    System.Console.WriteLine($"{key} - {value}");
}

Output:

DRA-1212 - The Godfather
COM-5465 - The Terminal
CLA-1659 - Casablanca

Accessing and updating dictionary items

A specific value may be accessed or modified using key subscript syntax to reference the corresponding value. The following code references a key known to be in the movies dictionary and outputs the associated value (in this case, the movie entitled “The Terminal” ):

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

System.Console.WriteLine(movies["COM-5465"]);

Output:

The Terminal

Indexing by key may also be used when updating the value associated with a specified key, for example, to change the title of the same movie from “The Terminal” to “Caddyshack” ):

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

movies["COM-5465"] = "Caddyshack";

System.Console.WriteLine(movies["COM-5465"]);

Output:

Caddyshack

Checking if a key or value exists

A dictionary can be searched to find out if it contains a particular key by calling the ContainsKey() method. Before adding new entries to a dictionary, you may want to call this method to ensure the key has not already been used to avoid a runtime exception error.

You can also check whether a specific value exists in a dictionary with a call to the ContainsValue() method. The following example demonstrates both of these methods in action:

var movies = new Dictionary<string, string>()
{
    { "DRA-1212", "The Godfather" },
    { "WAR-4433", "Apocalypse Now" },
    { "COM-5465", "The Terminal" },
    { "CLA-1659", "Casablanca" }
};

if (movies.ContainsKey("CLA-1659")) {
    System.Console.WriteLine("Key exists in dictionary");
}

if (movies.ContainsValue("The Godfather")) {
    System.Console.WriteLine("Title exists in dictionary");
}

Output:

Key exists in dictionary
Title exists in dictionary

C# 11 List Collections

In the previous chapters, we looked at C# Arrays. While useful for many tasks, arrays are starting to show their age in terms of functionality and flexibility. The C# Collection Classes provide more advanced mechanisms for gathering groups of objects.

What are the C# Collection Classes

The C# Collection classes are designed specifically for grouping objects and performing tasks on them. Several collection classes are available with C#, and we will look at some key classes in the next few chapters.

Creating C# List Collections with List<T>

The List<T> class has properties very similar to C# arrays. One key advantage of this class over arrays is that it can grow and shrink as the number of stored objects changes.

The List<T> class is contained within the  System.Collections.Generic  namespace, which must be imported when working with lists.

The syntax for creating a List<T> collection is as follows where type is replaced by the data type to be stored in the list:

List<type> name = new List<type>(); 

With the above syntax in mind, we could create an empty List<T> object named colorList configured to store string values as follows:

List<string> colorList = new List<string>();

Adding items to lists

Once you have created a List object instance, there are several methods you can call to perform tasks on the list. One such method is the Add() method which, as the name suggests, is used to add items to the list object:

List<string> colorList = new List<string>();

colorList.Add ("Red");
colorList.Add ("Green");
colorList.Add ("Yellow");
colorList.Add ("Purple");
colorList.Add ("Orange");

The Add() method appends the new item to the end of the list. To insert new items at a specific index location, you will need to use the Insert() method covered later in the chapter.

Initializing a list with multiple values

In the above example, we created an empty list and then added items one by one using the Add() method. However, if you need to initialize a list with a large number of values, you may find it more efficient to use a collection initializer, the syntax for which is as follows:

List<type> <list-name> = new List<type>()
{
    <item1>,
    <item2>,
    <item3>,
.
.
.
};

Using this syntax, we can modify our initialization example as follows:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

Accessing list items

Individual items in a list may be accessed using the item’s index value (keeping in mind that the first item is index 0, the second index 1, and so on). The index value is placed in square brackets after the list name. For example, to access the second item in the colorList object:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

System.Console.WriteLine (colorList[1]);

Output:

Green

A list item value can similarly be changed using the index combined with the assignment (=) operator. For example, to change the Yellow color to Ingido, the code would read as follows:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

System.Console.WriteLine ($"Color before = {colorList[2]}");
colorList[2] = "Indigo";
System.Console.WriteLine ($"Color after = {colorList[2]}");

Output:

Color before = Yellow
Color after = Indigo

As with arrays, you can also construct a foreach loop to list all of the items in a list. For example:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

foreach (string color in colorList)
{
    System.Console.Write($"{color} ");
}

Output:

Red Green Yellow Purple Orange

Removing items from lists

Items may be removed from a list using the Remove() method. This method takes the value of the item to be removed as an argument. For example, to remove the “Red” string from the colorList object, we could write the following code:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

colorList.Remove("Red");

foreach (string color in colorList)
{
    System.Console.Write($"{color} ");
}

Output:

Green Yellow Purple Orange

Note: C# liscan to store duplicate entries. In the case of duplicated items, the Remove() method will only remove the first matching instance.

Inserting items into a list

Previously we used the Add() method to add items to a list. The Add() method, however, only adds items to the end of a list. Sometimes it is necessary to insert a new item at a specific location in a list. The Insert() method is provided for this particular purpose.

Insert() takes two arguments, an integer indicating the index location of the insertion and the item to be inserted at that location. For example, to insert an item at location 2 in our example list:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

colorList.Insert(2, "White");

foreach (string color in colorList)
{
    System.Console.Write($"{color} ");
}

Output:

Red Green White Yellow Purple Orange 

Sorting lists in C#

There is no way to tell C# to automatically sort a list as items are added. The items in a list can be sorted into order by calling the Sort() method:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

colorList.Sort();

foreach (string color in colorList)
{
    System.Console.Write($"{color} ");
}

Output:

Green Orange Purple Red Yellow 

The above example uses the C# default comparison delegate to organize the new list order. Although beyond the scope of this book, it is worth knowing that for more advanced requirements, you can create your own comparison delegate.

Finding items in a C# list

Several methods are provided with the List class for finding items. The most basic method is the Contains() method which, when called on a list object, returns true if the specified item is located in the list, or false if it is not.

The IndexOf() method returns the index value of a matching item in a list. For example, the following code sample will output a true result indicating the presence of “Red” and the value 2, which is the index position of the “Yellow” string:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

System.Console.WriteLine(colorList.Contains("Red"));
System.Console.WriteLine(colorList.IndexOf("Yellow"));

Output:

True
2

If the item is not found in the list, a value of -1 is returned by the  IndexOf()  method.

The LastIndexOf() method returns the index value of the last item in the list to match the specified item. This is particularly useful when a list contains duplicate items:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Red",
    "Orange"
};

System.Console.WriteLine(colorList.LastIndexOf("Red"));

Output:

4

Obtaining information about a list

Two properties of the List<T> class are helpful in obtaining information about a list object. For example, the Capacity property can identify how many items a collection can store without resizing.

The Count property, however, identifies how many items are currently stored in the list. In general, the Capacity value will exceed the current Count:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

System.Console.WriteLine($"Count = {colorList.Count}");
System.Console.WriteLine($"Capacity = {colorList.Capacity}");

Output:

Count = 5
Capacity = 8

In instances where a gap exists between Count and Capacity you can remove excess capacity with a call the TrimExcess() method:

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

System.Console.WriteLine($"Count = {colorList.Count}");
colorList.TrimExcess();
System.Console.WriteLine($"Capacity = {colorList.Capacity}");

Output:

Count = 5
Capacity = 5

Clearing C# lists

You can remove all of the items in a list by making a call to the Clear() method.

The Clear() method removes the items from the list and sets the  Count  property to zero. The Capacity property, however, remains unchanged after the list has been cleared. To remove the capacity of a list, follow the Clear() method call with a call to TrimExcess():

List<string> colorList = new List<string>()
{
    "Red",
    "Green",
    "Yellow",
    "Purple",
    "Orange"
};

System.Console.WriteLine($"Count = {colorList.Count}");
System.Console.WriteLine($"Capacity = {colorList.Capacity}");

colorList.Clear();

System.Console.WriteLine($"Count = {colorList.Count}");
System.Console.WriteLine($"Capacity = {colorList.Capacity}");

colorList.TrimExcess();

System.Console.WriteLine($"Count = {colorList.Count}");
System.Console.WriteLine($"Capacity = {colorList.Capacity}");

Output:

Count = 5
Capacity = 8
Count = 0
Capacity = 8
Count = 0
Capacity = 0

Accessing and Sorting C# 11 Array Elements

The previous chapter explained how to create 2-dimensional, 3-dimensional, and jagged arrays in C#. In this chapter, we will explore how to access, remove, and sort the elements of an array.

Getting the number of dimensions of an array

The number of dimensions of an array can be obtained via the Rank property of the array. For example:

char[,] my2Darray = new char[3,3]
{
   {'a', 'b', 'c'}, 
   {'c', 'd', 'e'},
   {'c', 'd', 'e'}
};

char[,,] my3Darray = // new char[2,2,3] 
{ 
   { 
       { 'a', 'b', 'c' }, 
       { 'c', 'd', 'e' } 
   },
   { 
       { 'f', 'g', 'h' }, 
       { 'i', 'j', 'k' } 
   } 
};

System.Console.WriteLine($"Dimensions in my2Darray = {my2Darray.Rank}");
System.Console.WriteLine($"Dimensions in my3Darray = {my3Darray.Rank}");

Output:

Dimensions in my2Darray = 2
Dimensions in my3Darray = 3

Accessing array elements

Once values have been stored in an array, it is highly likely that these values will need to be accessed at some later point in the C# code. This is achieved using the array accessor notation combined with the index into the array of the desired value. The array accessor is simply the array name followed by square brackets ([]). Within the square brackets is placed a number representing the index into the array of the desired value (keeping in mind that the first array element in C# is index 0). For example, to access the second element of our myColors array, the following notation would be used:

string[] myColors = {"red", "green", "yellow", "orange", "blue"};

System.Console.WriteLine($"Element at index 1 = {myColors[1]}");

When executed, the above code will output the word “green” since this is the string contained at index position 1 in the array.

Similarly, the value at a particular index position in an array may be changed using the accessor notation combined with the assignment operator (=). For example, to change the value of the first item in the array:

string[] myColors = {"red", "green", "yellow", "orange", "blue"};

myColors[0] = "violet";

System.Console.WriteLine($"Element at index 0 = {myColors[0]}");

Values in a multidimensional array may be accessed by specifying the index values for each dimension separated by commas. For example, to access the first element of the first array of our my2Darray the following accessor would be used (keeping in mind that arrays start at index position 0):

char element = my2Darray[0,0];

The following code demonstrates accessing a variety of elements, both in 2D and 3D arrays:

char[,] my2Darray = new char[3,3]
{
   {'a', 'b', 'c'}, 
   {'d', 'e', 'f'},
   {'g', 'h', 'i'}
};

char[,,] my3Darray = 
{ 
   { 
       { 'a', 'b', 'c' }, 
       { 'd', 'e', 'f' } 
   },
   { 
       { 'g', 'h', 'i' }, 
       { 'j', 'k', 'l' } 
   } 
};

// Output first element of first array in my2Darray
System.Console.WriteLine($"{my2Darray[0,0]}");

// Output last element of last array in my2Darray
System.Console.WriteLine($"{my2Darray[2,2]}");

// Output 2nd element of 2nd array in 1st 2D array of my3Darray
System.Console.WriteLine($"{my3Darray[0,1,1]}");

// Output 3rd element of 2nd array in 2nd 2D array of my3Darray
System.Console.WriteLine($"{my3Darray[1,1,2]}");

Output:

a
i
e
l

Array iteration

The easiest way to iterate through the items in an array is to make use of the foreach looping syntax. The following code, for example, iterates through all of the items in an array and outputs each item to the console panel:

string[] myColors = {"red", "green", "yellow", "orange", "blue"};

foreach (string color in myColors)
{
    System.Console.Write("{0} ", color);
}

Output:

red green yellow orange blue

You can also use the foreach statement to iterate through multidimensional arrays. In the following example, we use foreach to iterate through all the elements of a three-dimensional array:

char[,,] my3Darray = 
{ 
   { 
       { 'a', 'b', 'c' }, 
       { 'c', 'd', 'e' } 
   },
   { 
       { 'f', 'g', 'h' }, 
       { 'i', 'j', 'k' } 
   } 
};

foreach (char letter in my3Darray)
{
    System.Console.Write("{0} ", letter);
}

Output:

a b c c d e f g h i j k

Working with ranges

The C# range (..) and index from end (^) operators are particularly useful for accessing subsets of array elements. Ranges are constructed using the following syntax where x and y represent the beginning and end range values, respectively:

x..y

Note: When working with ranges, it is important to remember that range syntax x..y encompasses all the numbers from x up to, but not including, y. The range operator 5…8, therefore, specifies the positions 5, 6, and 7.

In the absence of start or end positions, the range will include all possible values starting at 0 until the end of the array is reached:

..

One-sided range operators specify a range that can extend as far as possible in a specified range direction until the natural beginning or end of the range is reached (or until some other condition is met). A one-sided range is declared by omitting the number from one side of the range declaration, for example:

x…

or

…y

A range to specify two elements in an array starting with position 2 through to the last element could be declared as follows:

2…

Similarly, to specify a range that begins with the first element and ends with the element at position 5, the range would be specified as follows:

…6 

The following example extracts items 4 through 6 and assigns them to a new array named words:

string[] sentence = {"The", "best", "way", "to", "predict", "the", "future", "is", "to", "invent", "it"};

string[] words = sentence[4..7];

Similarly, we can use the following range syntax to extract words starting at element 0 and ending at element 4 follows:

string[] words = sentence[..5];

We can also combine the index operator with the range operator to perform operations similar to the following which extract elements starting at position 1 up until the second from last element of the array:

string[] words = sentence[1..^2];

Try the following code to see the above examples in action:

string[] sentence = {"The", "best", "way", "to", "predict", "the", "future", "is", "to", "invent", "it"};

string[] subset1 = sentence[^7..^4];
string[] subset2 = sentence[..^2];
string[] subset3 = sentence[3..^3];


foreach (string word in subset1)
{
    System.Console.Write("{0} ", word);
}

System.Console.WriteLine("\n");

foreach (string word in subset2)
{
    System.Console.Write("{0} ", word);
}

System.Console.WriteLine("\n");

foreach (string word in subset3)
{
    System.Console.Write("{0} ", word);
}

When the above code runs, it will generate the following output:

predict the future
The best way to predict
The best way to predict the future is to invent it

C# index from end operator

The C# index from end (^) operator allows you to specify the end of a range based on the number of positions from the end of the array.

The index ^1, for example, represents the last item in the array, ^2 the second from last item in the array, and so on. Index ^0 is already set to the total number of items in the array:

string[] myArray = {
    "item 1",  // <-- ^7 - 7th from last
    "item 2",  // <-- ^6
    "item 3",  // <-- ^5
    "item 4",  // <-- ^4
    "item 5",  // <-- ^3
    "item 6",  // <-- ^2 
    "item 7"   // <-- ^1 - Last item
               // ^0 = array length
};

The following code demonstrates the use of the index operator when working with ranges and arrays:

string[] sentence = {"The", "best", "way", "to", "predict", "the", "future", "is", "to", "invent", "it"};

string[] subset1 = sentence[^7..^4];
string[] subset2 = sentence[..^2];
string[] subset3 = sentence[3..^3];


foreach (string word in subset1)
{
    System.Console.Write("{0} ", word);
}

System.Console.WriteLine("\n");

foreach (string word in subset2)
{
    System.Console.Write("{0} ", word);
}

System.Console.WriteLine("\n");

foreach (string word in subset3)
{
    System.Console.Write("{0} ", word);
}

Output:

predict the future

The best way to predict the future is to

to predict the future is

Sorting C# arrays

The C# array is part of the System.Array package, which also includes some useful methods for sorting and re-ordering arrays.

If you need to sort an array, pass it as an argument to the Sort() method as follows:

string[] myColors = { "red", "green", "yellow", "orange", "blue" };

System.Console.WriteLine("Before Sort");

foreach (string color in myColors)
{
    System.Console.Write("{0} ", color);
}

System.Array.Sort(myColors);

System.Console.WriteLine("\n\nAfter Sort");

foreach (string color in myColors)
{
    System.Console.Write("{0} ", color);
}

Output:

Before Sort
red green yellow orange blue

After Sort
blue green orange red yellow

You can also reverse the order of the elements in an array using the  System.Array.Reverse()  method:

string[] myColors = {"red", "green", "yellow", "orange", "blue"};

System.Array.Reverse(myColors);

foreach (string color in myColors)
{
    System.Console.Write("{0} ", color);
}

Output:

blue orange yellow green red

Clearing C# arrays

The values in an array may be cleared using the System.Array.Clear() method. This method clears each item in an array to the default value for the type (false for Boolean values, 0 for numeric items, and null for strings). The syntax for clearing array elements is as follows:

System.Array.Clear(<array>, <start index>, <count>);

In the above syntax, <array> is the array to be cleared, <start index> is the position within the array at which clearing is to begin, and <count> is the number of elements to be cleared.

The following example clears three elements starting at item 1:

string[] myColors = {"red", "green", "yellow", "orange", "blue"};

System.Array.Clear(myColors, 1, 3);

foreach (string color in myColors)
{
    System.Console.Write("{0} ", color);
}

When the above code runs, only the blue and red elements will remain in the array.

Creating 2D, 3D, and Jagged Arrays in C# 11

Arrays are certainly not unique to C#. In fact, just about every other programming and scripting language preceding the introduction of C# provided support for arrays. An array allows a collection of values of the same type to be stored and accessed via a single variable. Each item is accessed in the array variable through the use of an array index.

C# arrays, whilst useful, have some limitations. Perhaps the most significant limitation is the fact that once an array has been created, it cannot be made larger or smaller to accommodate more or fewer values. More dynamic and flexible collection storage capabilities will be covered starting with the chapter titled C# 11 List Collections.

Creating arrays in C#

A C# array may be created in several different ways. One way is to declare an array without initializing it with any values. The syntax for this is as follows:

type[] arrayname;

In the above example, type represents the type of data to be stored in the array (or example, stringintdecimal etc). The square brackets ([]) indicate that this is the declaration for an array, and arrayname is the name by which the array is to be referred.

For example, we can declare an array of strings called myColors as follows:

string[] myColors;

In this example, we have declared the array but not assigned any values to it. To assign values after an array has been declared, the new statement must be used combined with a comma-separated list of values:

string[] myColors;
myColors = new string[] {"red", "green", "yellow", "orange", "blue"};

An array may also be initialized in the declaration line simply by placing the comma-separated list of values after the declaration:

string[] myColors = {"red", "green", "yellow", "orange", "blue"};

Another option is to declare the size of the array when it is created. For example, to declare an array of size 10, place the size value within the square brackets of the new statement:

myColors = new string[5];

This will reserve the space required for the full array without actually placing any values into the array. Finally, this approach may also be combined with the comma-separated value list (although the number of values must match the size specified):

string[5] myColors = {"red", "green", "yellow", "orange", "blue"};

Declaring multidimensional arrays

Multidimensional arrays are declared by placing commas within the square brackets. For example, to declare a two-dimensional array:

char[,] my2Darray; 

The single comma (,) in the above syntax indicates to C# that this is to a two-dimensional array. A two-dimensional array is initialized as follows:

char[,] my2Darray = 
{
   {'a', 'b', 'c'}, 
   {'c', 'd', 'e'},
   {'c', 'd', 'e'}
};

This creates a multidimensional array containing three rows and three columns.

When the array was declared above, the task of inferring the array dimensions was left to the C# compiler. These dimensions may also be specified when the array is declared, for example:

char[,] my2Darray = new char[3,3]
{
   {'a', 'b', 'c'}, 
.
.

The following, on the other hand, declares a three-dimensional array:

int[,,] my3Darray;

The following code will initialize the three-dimensional array:

char[,,] my3Darray =
{ 
   { 
       { 'a', 'b', 'c' }, 
       { 'c', 'd', 'e' } 
   },
   { 
       { 'f', 'g', 'h' }, 
       { 'i', 'j', 'k' } 
   } 
};

A three-dimensional array is essentially an array where each element is a two-dimensional array. In the above declaration, the array consists of two arrays, each of which contains a two-dimensional array, each containing three elements. This translates to an array with three dimensions of 2, 2, and 3.

It is also possible to specify the dimensions when the array is declared as follows:

char[,,] my3Darray = new char[2,2,3] 
{ 
   { 
       { 'a', 'b', 'c' }, 
.
.

Declaring jagged arrays

A jagged array is an array where each element is itself an array. The term jagged is used in this context because the array elements can be of different lengths. A jagged array can be thought of as a 2D array where each array can have a different number of elements.

The following is an example of a declaration for a jagged array designed to hold two-dimensional arrays of variable sizes:

char[][] myJaggedArray; 

The following code declares and initializes the same jagged array:

char[][] myJaggedArray =
{
    new char[] { 'a', 'b', 'c', 'd', 'e' },
    new char[] { 'f', 'g', 'h', 'i' },
    new char[] { 'j', 'j' },
    new char[] { 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r' }
};

C# 11 Structures

Having covered C# classes in the preceding chapters, this lesson will introduce the use of structures in C#. Although at first glance structures and classes look similar, there are some important differences that need to be understood when deciding which to use. This chapter will outline how to declare and use structures, explore the differences between structures and classes and introduce the concepts of value and reference types.

An overview of C# structures

As with classes, structures form the basis of object-oriented programming and provide a way to encapsulate data and functionality into reusable instances. Structure declarations resemble classes with the exception that the struct keyword is used in place of the class keyword. The following code, for example, declares a simple structure consisting of a String variable, initializer, and method:

struct SampleStruct {
    
    private string _name;
    
    public string Name {
        get { return _name; }
        set { _name = value; }
    }

    public SampleStruct(string name) {
        _name = name;
    }
    
    public string BuildHelloMsg() {
        return "Hello " + _name;
    }
}

Consider the above structure declaration in comparison to the equivalent class declaration:

public class SampleClass {
    
    private string _name;
    
    public string Name {
        get { return _name; }
        set { _name = value; }
    }

    public SampleStruct(string name) {
        _name = name;
    }
    
    public string BuildHelloMsg() {
        return "Hello " + _name;
    }
}

Other than the use of the struct keyword instead of class, the two declarations are identical. Instances of each type are also created using the same syntax:

SampleStruct demoStruct = new SampleStruct(name: "Alex");
SampleClass demoClass = new SampleClass(name: "Anna"); 

Given the commonality between classes and structures, it is important to gain an understanding of how the two differ. Before exploring the most significant difference it is first necessary to understand the concepts of value types and reference types.

Value types vs. reference types

While on the surface structures and classes look alike, major differences in behavior occur when structure and class instances are copied or passed as arguments to methods. This occurs because structure instances are value type while class instances are reference type.

When a structure instance is copied or passed to a method, an actual copy of the instance is created, together with any data contained within the instance. This means that the copy has its own version of the data which is unconnected with the original structure instance. In effect, this means that there can be multiple copies of a structure instance within a running app, each with its own local copy of the associated data. A change to one instance has no impact on any other instances.

In contrast, when a class instance is copied or passed as an argument, the only thing duplicated or passed is a reference to the location in memory where that class instance resides. Any changes made to the instance using those references will be performed on the same instance. In other words, there is only one class instance but multiple references pointing to it. A change to the instance data using any one of those references changes the data for all other references.

To demonstrate reference and value types in action, consider the following code:

class Example
{
    public struct SampleStruct {
        
        private string _name;
        
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        public SampleStruct(string name) {
            _name = name;
        }
        
        public string BuildHelloMsg() {
            return "Hello " + _name;
        }
    }

    static void Main()
    {
        SampleStruct myStruct1 = new SampleStruct(name: "Mark");
        System.Console.WriteLine(myStruct1.BuildHelloMsg());
    }
}

When the code executes, the name “Mark” will be displayed. Now change the code so that a copy of the myStruct1 instance is made, the name property is changed and the names from each instance are displayed in the message:

class Example
{
    public struct SampleStruct {
        
        private string _name;
        
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        public SampleStruct(string name) {
            _name = name;
        }
        
        public string BuildHelloMsg() {
            return "Hello " + _name;
        }
    }

    static void Main()
    {
        SampleStruct myStruct1 = new SampleStruct(name: "Mark");
        SampleStruct myStruct2 = myStruct1;
        myStruct2.Name = "David";

        System.Console.WriteLine(myStruct1.BuildHelloMsg());
        System.Console.WriteLine(myStruct2.BuildHelloMsg());
    }
}

When executed, the output will read as follows:

Hello Mark
Hello David

Clearly, the change of name only applied to myStruct2 since this is an actual copy of myStruct1 containing its own copy of the data as shown in the following figure:

Figure 1-1

Contrast this with the following class example:

class Example
{
    public class SampleClass {
        
        private string _name;
        
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        public SampleClass(string name) {
            _name = name;
        }
        
        public string BuildHelloMsg() {
            return "Hello " + _name;
        }
    }

    static void Main()
    {
        SampleClass myClass1 = new SampleClass(name: "Mark");
        SampleClass myClass2 = myClass1;
        myClass2.Name = "David";

        System.Console.WriteLine(myClass1.BuildHelloMsg());
        System.Console.WriteLine(myClass2.BuildHelloMsg());
    }
}

When this code executes, the following output will be generated:

Hello David
Hello David

In this case, the name property change is reflected for both myClass1 and myClass2 because both are references pointing to the same class instance as illustrated in the figure below:

Figure 1-2

In addition to these value and reference type differences, structures do not support inheritance and subclassing in the way that classes do. In other words, it is not possible for one structure to inherit from another structure. Unlike classes, structures also cannot contain a finalizer method. Structure members cannot be specified as abstract, virtual, or protected. Structures can, however, implement interfaces.

Read-only structures

Structures can be declared as being read-only using the readonly modifier. When declared in this way, all data members of the structure must also be declared as being read-only. In the case of field members, this requires the use of the readonly modifier. Properties, on the other hand, must either be declared as readonly or use the init-only accessors (a topic covered in the chapter entitled An Introduction to C# 11 Object-Oriented Programming).

The following structure, for example, includes both a read-only field and an init-only property:

class Example
{
    public readonly struct SampleStruct {
        
        private readonly string _name;
        
        public string Name {
            get { return _name; }
          //  set { _name = value; }
        }

        public SampleStruct(string name) {
            _name = name;
        }
        
        public string BuildHelloMsg() {
            return "Hello " + _name;
        }
    }

    static void Main()
    {
        SampleStruct myStruct1 = new SampleStruct(name: "Mark");
        SampleStruct myStruct2 = myStruct1;
       // myStruct2.Name = "David";

        System.Console.WriteLine(myStruct1.BuildHelloMsg());
        System.Console.WriteLine(myStruct2.BuildHelloMsg());
    }
}

C# 11 Interfaces

In this chapter, we will explain what C# interfaces are, how to write your own, and how to make other classes comply with one.

Understanding C# interfaces

By default, there are no specific rules to which a C# class must conform as long as the class is syntactically correct. In some situations, however, a class will need to meet certain criteria in order to work with other classes. This is particularly common when writing classes that need to work with other libraries. A set of rules that define the minimum requirements that a class must meet is referred to as an interface. An interface is declared using the interface keyword and simply defines the methods and properties that a class must contain in order to be in conformance. When a class adopts an interface but does not meet all of the interface requirements, errors will be reported stating that the class fails to conform to the interface.

Consider the following interface declaration. Any classes that adopt this interface must include a method named ShowMessage() which accepts no parameters and returns a string value:

interface IMessageBuilder {
    void ShowMessage();
}

Note: Interfaces are usually named with “I” as the first letter to differentiate them from non-interface classes.

Below, a class has been declared which adopts the IMessageBuilder interface but fails to compile:

class Example
{
    interface IMessageBuilder {
        void ShowMessage();
    }

    public class MyMessageBuilder : IMessageBuilder {

    }
 
    static void Main()
    {
        
    }
}

When compiled, the above code will result in an error similar to the following:

main.cs(7,18): error CS0535: `Example.MyMessageBuilder' does not implement interface member `Example.IMessageBuilder.ShowMessage()'
main.cs(4,14): (Location of the symbol related to previous error)

Unfortunately, as currently implemented, MyMessageBuilder generates a compilation error because it does not contain an implementation of the ShowMessage() method as required by the interface it has adopted. To conform to the interface, the class would need to meet this requirement, for example:

class Example
{
    interface IMessageBuilder { 
        void ShowMessage();
    }

    public class MyMessageBuilder : IMessageBuilder {
        public void ShowMessage() {
            // Code goes here.
        }
    }
 
    static void Main()
    {
        
    }
}

In addition to methods, interfaces can also include data members. An important rule when working with interfaces is that variables must be declared as properties and cannot be declared as fields or constants (for a reminder of the differences between fields and properties, refer back to the lesson entitled An Introduction to C# 11 Object-Oriented Programming).

The following, for example, would generate a syntax error during compilation:

interface IMessageBuilder { 
    string myMessage;  // Fields are invalid in interfaces
    void ShowMessage();
}

Instead, the variable needs to be declared as a property as demonstrated below:

class Example
{
    interface IMessageBuilder {

        string MyMessage { get; set; } 
        void ShowMessage();
    }

    public class MyMessageBuilder : IMessageBuilder {

        private string _myMessage;

        public string MyMessage { 
            get { return _myMessage; }
            set { _myMessage = value; }
        }

        public void ShowMessage() {
            System.Console.WriteLine(_myMessage);
        }
    }
 
    static void Main()
    {
        MyMessageBuilder builder = new MyMessageBuilder();
        builder.MyMessage = "Hello Interfaces!";
        builder.ShowMessage();
    }
}

Note that  get and set accessor methods are declared but not implemented in the above interface. As interface methods, the implementation is performed in the conforming class declaration.

C# 11 Abstract Classes

In the preceding chapters, we have looked in detail at object-oriented programming in C# and also at the concept of class inheritance. In this lesson, we will look at the next area of object-oriented programming, the abstract class.

What is a C# abstract class?

In the examples we have looked at so far in this book, we have created classes that could be both instantiated as objects and used as a base class from which to derive classes. Often a base class is not intended to be instantiated and is provided solely to provide an outline or template for subclasses. Such a class is known as an abstract class. An abstract class cannot be instantiated as an object and is only provided to derive subclasses.

Abstract members

A C# abstract class contains abstract members, which define what a subclass must contain. These abstract members declare that a member of a particular type is required; it does not implement the member. Implementation of abstract members takes place within the derived class. A subclass that derives from an abstract class and fails to implement abstract methods will generate syntax errors at compile time.

Declaring a C# abstract class

In addition to using the class modifier, as we have seen in previous examples, abstract classes must also be declared using the abstract modifier:

Abstract classes are declared using the abstract modifier placed before the class modifier:

public abstract class Talk {
}

Abstract class member methods and properties are also declared using the abstract keyword. For example, to declare an abstract method in our Talk class, the following code is required:

public abstract class Talk {
     public abstract void Speak();
}

We now have an abstract class with an abstract method named Speak(). Note that this declaration only states that any class derived from the Talk base class must implement a method called Speak() which returns no value (i.e., it is declared as void) and has no parameters. It does not, however, implement the method.

Deriving from an abstract class

To subclass from an abstract class, we simply write code as follows:

public class SayHello : Talk {
}

We now have a class named SayHello , which is derived from the abstract Talk class. The next step is to implement the abstract Speak() method. The override modifier must be used when implementing abstract members in a derived class. For example:

public override void Speak() {
    Console.WriteLine("Hello!");
}

We now have a subclass derived from the Talk abstract class, which implements the abstract Speak() method.

We can now bring all of this together into a simple program:

class DemoClass
{
    public abstract class Talk
    {
        public abstract void Speak();
    }
  
    public class SayHello : Talk
    {
        public override void Speak()
        {
            System.Console.WriteLine("Hello!");
        }
    }
  
    static void Main()
    {
        SayHello demo = new SayHello();
        demo.Speak();
    }
}

The difference between abstract and virtual members

So far, we have only looked at abstract class members. As discussed above, an abstract member is not implemented in the base class and must be implemented in derived classes for the class to compile.

Another type of member is a virtual member. A member defined as virtual must be implemented in the base class but may optionally be overridden in the derived class if different behavior is required. Such methods are declared using the virtual modifier in the base class.

For example, the following example implements a virtual method in the Talk class:

class DemoClass
{
    public abstract class Talk
    {
        public abstract void Speak();

        public virtual void Goodbye()
		{
			System.Console.WriteLine("Talk class says goodbye!");
		}

    }
  
    public class SayHello : Talk
    {
        public override void Speak()
        {
            System.Console.WriteLine("Hello!");
        }
    }
  
    static void Main()
    {
        SayHello demo = new SayHello();
        demo.Speak();
        demo.Goodbye();
    }
}

If we decide that the default Goodbye() method provided by the Talk class is not suitable for the requirements of the SayHello subclass, we can simply implement our own version of the method using the override modifier:

class DemoClass
{
    public abstract class Talk
    {
        public abstract void Speak();

        public virtual void Goodbye()
		{
			System.Console.WriteLine("Talk class says goodbye!");
		}

    }
  
    public class SayHello : Talk
    {
        public override void Speak()
        {
            System.Console.WriteLine("Hello!");
        }

        public override void Goodbye()
        {
            base.Goodbye();
            System.Console.WriteLine ("SayHello class says goodbye!");
        }
    }
  
    static void Main()
    {
        SayHello demo = new SayHello();
        demo.Speak();
        demo.Goodbye();
    }
}

Now when the code executes, the implementation of the Goodbye() method in the subclass is called.

As with non-abstract classes, a subclass may call the base class implementation of a virtual method. For example, the subclass version of the Goodbye() method could be modified to also call the overridden base class Goodbye() method as follows:

public override void Goodbye()
{
    base.Goodbye();
    System.Console.WriteLine ("SayHello class says goodbye!");
}