1. Hello C#

Overview

This chapter introduces you to the basics of C#. You will start by learning about the basics of the .NET Command-Line Interface (CLI) and how to use Visual Studio Code (VS Code) as a basic Integrated Development Environment (IDE). You will then learn about the various C# data types and how to declare variables for these types, before moving on to a section about arithmetic and logical operators. By the end of the chapter, you will know how to handle exceptions and errors and be able to write simple programs in C#.

Introduction

C# is a programming language created in the early 2000s by a team at Microsoft led by Anders Hejlsberg, who is also among the creators of some other popular languages, such as Delphi and Turbo Pascal, both widely used in the 1990s. Over the last 20 years, C# has grown and evolved, and today it is one of the most widely used programming languages globally, according to Stack Overflow's 2020 insights.

It has its reasons for holding such an honorable place in the tech community. C# allows you to write applications for a wide segment of markets and devices. From the banking industry, with its high-security standards, to e-commerce companies, which hold enormous volumes of transactions, it is a language trusted by companies that need both performance and reliability. Besides that, C# also makes it possible to write web, desktop, mobile, and even IoT applications, allowing you to develop for almost every kind of device.

C# was initially limited to work only on Windows; however, there have been concerted efforts by the C# team over the past few years to make it cross-platform compatible. Today, it can be used with all major OS distributions, namely, Windows, Linux, and macOS. The goal is simple: to develop, build, and run C# anywhere, letting each developer and team choose their most productive or favorite environment.

Another remarkable characteristic of C# is that it is a strongly typed programming language. You will dive into this more deeply in the upcoming sections, and you will see that strong typing enables better data security while programming.

Besides that, C# has become open source over the last few years, with Microsoft as its principal maintainer. This is highly advantageous, as it allows the language to receive continuous improvements from around the globe, with a solid backing company that both promotes and invests in it. C# is also a multi-paradigm language, meaning that you can use it to write software in many programming styles, in a beautiful, concise, and proper manner.

Running and Developing C# with the .NET CLI

One term you'll hear a lot in the C# world is .NET. It is the foundation of C#, a framework that the language is built on top of. It has both a Software Development Kit (SDK) that allows the language to be developed and a runtime that allows the language to run.

That said, to start developing with C#, you only need to install the .NET SDK. This installation will provide both a compiler and the runtime on the development environment. In this section, you will cover the basic steps of preparing your environment for developing and running C# locally.

Note

Please refer to the Preface of this book for step-by-step instructions on how to download the .NET 6.0 SDK and install it on your machine.

Once the installation of the .NET 6.0 SDK is completed, you will have something called the .NET CLI. This Command-Line Interface (CLI) allows you to create new projects, compile them, and run them with very simple commands that you can run directly from your terminal.

After the installation, run the following command on your favorite terminal:

dotnet --list-sdks

You should see an output like this:

6.0.100 [/usr/local/share/dotnet/sdk]

This output shows that you have the 6.0.100 version of the SDK installed on your computer. That means you are ready to start developing your applications. If you type dotnet -–help, you will notice that several commands will appear for you as options to run within the CLI. In this section, you will cover the most basic ones that you need to create and run applications: new, build, and run.

The dotnet new command allows you to create a bootstrap project to start developing. The CLI has several built-in templates, which are nothing more than basic bootstraps for various types of applications: web apps, desktop apps, and so on. You must specify two things in the dotnet new command:

  • The template name
  • The project name

The name is passed as an argument, which means you should specify it with a -n or –name flag. The command is as follows:

dotnet new TYPE -n NAME

For instance, to create a new console application named MyConsoleApp you can simply type:

dotnet new console -n MyConsoleApp

This will generate a new folder with a file named MyConsoleApp.csproj, which is the C# project file that contains all the metadata needed by the compiler to build your project, and some files needed for the application to be built and run.

Next, the dotnet build command allows you to build an application and make it ready to run. This command should be placed only in two locations:

  • A project folder, containing a .csproj file.
  • A folder containing a .sln file.

Solution (.sln) files are files that contain the metadata of one or more project files. They are used to organize multiple project files into single builds.

Finally, the third important command is dotnet run. This command allows you to properly run an application. It can be called without any arguments from the folder that contains the .csproj file of your .NET app, or without passing the project folder with the -–project flag on the CLI. The run command also automatically builds the application prior to the run.

Creating Programs with the CLI and VS Code

While working through this book, you will use Visual Studio Code (VS Code) as your code editor. It works on all platforms, and you can download the version for your OS at https://code.visualstudio.com/. Although VS Code is not a complete Integrated Development Environment (IDE), it has a lot of extensions that make it a powerful tool to develop and do proper C# coding, regardless of the OS being used.

To properly develop C# code, you will primarily need to install the Microsoft C# extension. It equips VS Code with the ability to do code completion and identify errors and is available at https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp.

Note

Before proceeding, it is recommended that you install VS Code and the Microsoft C# extension. You can find a step-by-step breakdown of the installation process in the Preface of this book.

Basic Anatomy of a C# Program

In order to run, every C# program needs something called an entry point. In C#, the standard entry point for a program is the Main method. Regardless of your program type, whether it is a web application, desktop application, or even a simple console one, the Main method will be the entry point for your C# program. This means that each time an application runs, the runtime searches for this method within your code and executes the code blocks inside it.

This structure is created for you by the CLI, with the new command. A Program.cs file contains a class named Program, with a method named Main, which, in turn, contains a single instruction that will be executed after the program is built and running. You will learn more about methods and classes later, but for now, just know that a class is something that usually contains a set of data and that can perform actions on this data through these methods.

Another important thing to note regarding basic C# concepts is comments. Comments allow you to place free text inside C# code files, without affecting the compiler. A comment section should always start with //.

Exercise 1.01: Creating a Console App that Says "Hello World"

In this exercise, you will see the CLI commands you learned about in the previous section, as you build your first ever C# program. It will be a simple console app that will print Hello World to the console.

Perform the following steps to do so:

  1. Open the VS Code integrated terminal and type the following:

    dotnet new console -n Exercise1_01

This command will create a new console application in the Exercise1_01 folder.

  1. On the command line, type the following:

    dotnet run --project Exercise1_01

You should see the following output:

Figure 1.1: "Hello World" output on the console

Figure 1.1: "Hello World" output on the console

Note

You can find the code used for this exercise at https://packt.link/HErU6.

In this exercise, you created the most basic program possible with C#, a console application that prints some text to the prompt. You also learned how to use .NET CLI, which is the mechanism built within the .NET SDK to create and manage .NET projects.

Now proceed to the next section to grasp how top-level statements are written.

Top-Level Statements

You must have noticed in Exercise 1.01 that, by default, when you create a console application, you have a Program.cs file that contains the following:

  • A class named Program.
  • The static void Main keywords.

You will learn about classes and methods in detail later, but for now, for the sake of simplicity, you do not need these resources to create and execute programs with C#. The latest version (.NET 6) introduced a feature that makes writing simple programs much easier and less verbose. For instance, consider the following:

using System;

namespace Exercise1_01

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Hello World!");

        }

    }

}

You can simply replace this snippet with two lines of code, as follows:

using System;

Console.WriteLine("Hello World!");

By using such top-level statements, you can write concise programs. You can simply put the statements to be executed at the top of the program. This is also useful for speeding up the learning curve with C#, as you need not worry about advanced concepts upfront. The only thing to look out for here is that the project can have only one file with top-level statements.

That is why in this chapter, you will find that all exercises will use this format, to make things as clear as possible.

Declaring Variables

You will now take your first steps in creating your own programs. This section will delve into the concept of variables—what they are and how to use them.

A variable is a name given to a computer memory location that holds some data that may vary. For a variable to exist, it first must be declared with a type and a name. It can also have a value assigned to it. The declaration of a variable can be achieved in a few different ways.

There are some basic considerations regarding naming conventions for variables in C#:

  • The names must be unique, starting with a letter, and should contain only letters, digits, and the underscore character (_). The names can also begin with an underscore character.
  • The names are case-sensitive; thus, myVariable and MyVariable are different names.
  • Reserved keywords, such as int or string, cannot be used as names (this is a compiler restriction) unless you put an @ symbol in front of the name, such as @int or @string.

Variables can be declared in two ways: explicitly and implicitly. Both styles of the declaration have their pros and cons, which you will explore in the next section.

Declaring Variables Explicitly

A variable can be declared explicitly by writing both its type and value. Suppose you want to create two variables, a and b, both containing integers. Doing so explicitly would look like this:

int a = 0;

int b = 0;

Before a variable is used, it must have a value assigned. Otherwise, the C# compiler will give an error while building your program. The following example illustrates that:

int a;

int b = a; // The compiler will prompt an error on this line: Use of unassigned local variable

It is also possible to declare multiple variables in the same line, like in the following snippet, where you are declaring three variables; two hold the value 100 and one holds the value 10:

int a, b = 100, c = 10;

Declaring Variables Implicitly

Remember that C# is a strongly typed programming language; this means that a variable will always have a type associated with it. It does not matter whether the type is declared implicitly or explicitly. With the var keyword, the C# compiler will infer the variable type based on the value that has been assigned to it.

Consider that you want to create a variable that holds some text using this method. This can be done with the following statement:

var name = "Elon Musk";

For storing text in a variable, you should start and end the text with double quotes ("). In the preceding example, by looking at the value that was assigned to name, C# knows that the type this variable holds is a string, even though the type is not mentioned in the statement.

Explicit versus Implicit Declaration

Explicit declarations enhance readability with the type declared, and this is one of the main advantages of this technique. On the other hand, they tend to let the code become more verbose, especially when working with some data types (that you will see further ahead), such as Collections.

Essentially, deciding on the style of declaration depends on the personal preferences of the programmer, and may be influenced by the company's guidelines in some cases. In this journey of learning, it is recommended that you pick one that makes your learning path smoother, as there are few substantial differences from a purely technical standpoint.

In the next exercise, you will do this yourself by assigning variables to inputs that come from a user's interaction with a console application, where the user will be asked to input their name. To complete this exercise, you will make use of the following built-in methods that C# provides, which you will be using frequently in your C# journey:

  • Console.ReadLine(): This allows you to retrieve a value that the user prompted on the console.
  • Console.WriteLine(): This writes the value passed as an argument as an output to the console.

Exercise 1.02: Assigning Variables to User Inputs

In this exercise, you will create an interactive console application. The app should ask you for your name, and once provided, it should display a greeting with your name in it.

To complete this exercise, perform the following steps:

  1. Open Command Prompt and type the following:

    dotnet new console -n Exercise1_02

This command creates a new console application in the Exercise1_02 folder.

  1. Open the Program.cs file. Paste the following inside the Main method:

    Console.WriteLine("Hi! I'm your first Program. What is your name?");

    var name = Console.ReadLine();

    Console.WriteLine($"Hi {name}, it is very nice to meet you. We have a really fun journey ahead.");

  2. Save the file. On the command line, type the following:

    dotnet run --project Exercise1_02

This outputs the following:

Hi! I'm your first Program. What is your name?

  1. Now, type your name into the console and hit Enter on your keyboard. For example, if you type in Mateus, the following will be the output:

    Hi! I'm your first Program. What is your name?

    Mateus

    Hi Mateus, it is very nice to meet you. We have a really fun journey ahead.

    Note

    You can find the code used for this exercise at https://packt.link/1fbVH.

You are more familiar with what variables are, how to declare them, and how to assign values to them. Now it is time to start talking about what data these variables can store and, more specifically, what types of data there are.

Data Types

In this section, you will talk about the main data types within C# and their functionalities.

Strings

C# uses the string keyword to identify data that stores text as a sequence of characters. You can declare a string in several ways, as shown in the following snippet. However, when assigning some value to a string variable, you must place the content between a pair of double quotes, as you can see in the last two examples:

// Declare without initializing.

string message1;

// Initialize to null.

string message2 = null;

// Initialize as an empty string

string message3 = System.String.Empty;

// Will have the same content as the above one

string message4 = "";

// With implicit declaration

var message4 = "A random message" ;

One simple but effective technique (that you used in the preceding Exercise 1.02) is one called string interpolation. With this technique, it is very simple to mix plain text values with variable values, so that the text is combined among these two. You can combine two or more strings by following these steps:

  1. Before the initial quotes, insert a $ symbol.
  2. Now, inside the strings, place curly brackets and the name of the variable that you want to put into the string. In this case, this is done by putting {name} inside the initial string:

    $"Hi {name}, it is very nice to meet you. We have a really fun journey ahead.");

Another important fact to remember about strings is that they are immutable. This means that a string object cannot be changed after its creation. This happens because strings in C# are an array of characters. Arrays are data structures that gather objects of the same type and have a fixed length. You will cover arrays in detail in an upcoming section.

In the next exercise, you will explore string immutability.

Exercise 1.03: Checking String Immutability

In this exercise, you will use two strings to demonstrate that string references are always immutable. Perform the following steps to do so:

  1. Open the VS Code integrated terminal and type the following:

    dotnet new console -n Exercise1_03

  2. Open the Program.cs file and create a method with the void return type, which replaces part of a string like so:

    static void FormatString(string stringToFormat)

    {

    stringToFormat.Replace("World", "Mars");

    }

In the preceding snippet, the Replace function is used to replace the first string (World, in this case) with the second one (Mars).

  1. Now, create a method that does the same thing but returns the result instead:

    static string FormatReturningString(string stringToFormat)

    {

    return stringToFormat.Replace("Earth", "Mars");

    }

  2. Now insert the following after the previous methods. Here, you create two string variables and observe their behavior after trying to modify them with the methods created previously:

    var greetings = "Hello World!";

    FormatString(greetings);

    Console.WriteLine(greetings);

    var anotherGreetings = "Good morning Earth!";

    Console.WriteLine(FormatReturningString(anotherGreetings));

  3. Finally, call dotnet run --project Exercise1_03 from the command line. You should see the following output on the console:

    dotnet run

    Hello World!

    Good morning Mars!

    Note

    You can find the code used for this exercise at https://packt.link/ZoNiw.

With this exercise, you saw the concept of string immutability in action. When you passed a string that was a reference type (Hello World!) as a method argument, it was not modified. That is what happens when you use the FormatString method, which returns void. Due to string immutability, a new string is created but not allocated to any variable, and the original string stays the same. With the second method, it returns a new string, and this string is then printed to the console.

Comparing Strings

Even though strings are reference values, when you use the .Equals() method, the equality operator (==), and other operators (such as !=), you are actually comparing the values of the strings, as can be seen in the following example:

string first = "Hello.";

string second = first;

first = null;

Now you can compare these values and call Console.WriteLine() to output the result, like so:

Console.WriteLine(first == second);

Console.WriteLine(string.Equals(first, second));

Running the preceding code results in the following output:

False

False

You get this output because, even though strings are reference types, both the == and .Equals comparisons run against string values. Also, remember that strings are immutable. This means that when you assign second to first and set first as null, a new value is created for first and, therefore, the reference for second does not change.

Numeric Types

C# has its numeric types subdivided into two main categories—integral and floating-point type numbers. The integral number types are as follows:

  • sbyte: Holds values from -128 to 127
  • short: Holds values from -32,768 to 32,767
  • int: Holds values from -2,147,483,648 to 2,147,483,647
  • long: Holds values from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Deciding which type of integral type to use depends on the size of the values you want to store.

All these types are called signed values. This means that they can store both negative and positive numbers. There is also another range of types called unsigned types. Unsigned types are byte, ushort, uint, and ulong. The main difference between them is that signed types can store negative numbers and unsigned types can store only numbers greater than or equal to zero. You will use signed types most of the time, so do not worry about remembering this all at once.

The other category, namely, floating-point types, refers to the types used to store numbers with one or more decimal points. There are three floating-point types in C#:

  • float: This occupies four bytes and can store numbers from ± 1.5 x 10−45 to ± 3.4 x 1038 with a precision range of six to nine digits. To declare a float number using var, you can simply append f to the end of the number, like so:

    var myFloat = 10f;

  • double: This occupies eight bytes and can store numbers from ± 5.0 × 10−324 to ± 1.7 × 1030 with a precision range of 15 to 17 digits. To declare a double number using var, you can append d to the end of the number, like so:

    var myDouble = 10d;

  • decimal: This occupies 16 bytes and can store numbers from ± 1.0 x 10-28 to ± 7.9228 x 1028 with a precision range from 28 to 29 digits. To declare a decimal number using var, you must simply append m to the end of the number, like so:

    var myDecimal = 10m;

Choosing the floating-point type depends mainly on the degree of precision required. For instance, decimal is mostly used for financial applications that need a very high degree of precision and cannot rely on rounding for accurate calculations. With GPS coordinates, double variables might be appropriate if you want to deal with sub-meter precisions that usually have 10 digits.

Another relevant point to consider when choosing numeric types is performance. The larger the memory space allocated to a variable, the less performant the operations with these variables are. Therefore, if high precision is not a requirement, float variables will be better performers than doubles, which, in turn, will be better performers than decimals.

Here you grasped what variables are and their main types. Now you will perform some basic calculations with them, such as addition, subtraction, and multiplication. This can be done using the arithmetic operators available in C#, such as +, -, /, and *. So, move on to the next exercise where you will create a basic calculator using these operators.

Exercise 1.04: Using the Basic Arithmetic Operators

In this exercise, you will create a simple calculator that receives two inputs and shows the results between them, based on which arithmetic operation is selected.

The following steps will help you complete this exercise:

  1. Open the VS Code integrated terminal and type the following:

    dotnet new console -n Exercise1_04

  2. Navigate to the project folder, open the Program.cs file, and inside the Main method, declare two variables that read the user input, like so:

    Console.WriteLine("Type a value for a: ");

    var a = int.Parse(Console.ReadLine());

    Console.WriteLine("Now type a value for b: ");

    var b = int.Parse(Console.ReadLine());

The preceding snippet uses the .ReadLine method to read the input. This method, however, gives a string, and you need to evaluate a number. Therefore, the Parse method has been used here. All the numeric types have a method called Parse, which receives a string and converts it into a number.

  1. Next, you need to write the output of these basic operators to the console. Add the following code to the Main method:

    Console.WriteLine($"The value for a is { a } and for b is { b }");

    Console.WriteLine($"Sum: { a + b}");

    Console.WriteLine($"Multiplication: { a * b}");

    Console.WriteLine($"Subtraction: { a - b}");

    Console.WriteLine($"Division: { a / b}");

  2. Run the program using the dotnet run command, and you should see the following output, if you input 10 and 20, for instance:

    Type a value for a:

    10

    Now type a value for b:

    20

    The value for a is 10 and b is 20

    Sum: 30

    Multiplication: 200

    Subtraction: -10

    Division: 0

    Note

    You can find the code used for this exercise at https://packt.link/ldWVv.

Thus, you have built a simple calculator app in C# using the arithmetic operators. You also learned about the concept of parsing, which is used to convert strings to numbers. In the next section, you will briefly cover the topic of classes, one of the core concepts of programming in C#.

Classes

Classes are an integral part of coding in C# and will be covered comprehensively in Chapter 2, Building Quality Object-Oriented Code. This section touches upon the basics of classes so that you can begin using them in your programs.

The reserved class keyword within C# is used when you want to define the type of an object. An object, which can also be called an instance, is nothing more than a block of memory that has been allocated to store information. Given this definition, what a class does is act as a blueprint for an object by having some properties to describe this object and specifying the actions that this object can perform through methods.

For example, consider that you have a class named Person, with two properties, Name and Age, and a method that checks whether Person is a child. Methods are where logic can be placed to perform some action. They can return a value of a certain type or have the special void keyword, which indicates that they do not return anything but just execute some action. You can also have methods calling other methods:

public class Person

{

public Person() { }

public Person(string name, int age)

{

Name = name;

Age = age;

}

public string Name { get; set; }

public int Age { get; set; }

public void GetInfo()

{

Console.WriteLine($"Name: {Name} – IsChild? {IsChild()}");

}

public bool IsChild()

{

return Age < 12;

}

}

One question remains, though. Since classes act as blueprints (or definitions if you prefer), how do you actually allocate memory to store the information defined by a class? This is done through a process called instantiation. When you instantiate an object, you allocate some space in memory for it in a reserved area called the heap. When you assign a variable to an object, you are setting the variable to have the address of this memory space, so that each time you manipulate this variable, it points to and manipulates the data allocated at this memory space. The following is a simple example of instantiation:

var person = new Person();

Note that Person has properties that have two magic keywords—get and set. Getters define that a property value can be retrieved, and setters define that a property value can be set.

Another important concept here is the concept of a constructor. A constructor is a method with no return type, usually present at the top level of the class for better readability. It specifies what is needed for an object to be created. By default, a class will always have a parameter-less constructor. If another constructor with parameters is defined, the class will be constrained to only this one. In that case, if you still want to have a parameter-less constructor, you must specify one. This is quite useful, as classes can have multiple constructors.

That said, you can assign values to an object property that has a setter in the following ways:

  • At the time of creation, via its constructor:

    var person = new Person("John", 10);

  • At the time of creation, with direct variable assignment:

    var person = new Person() { Name = "John", Age = 10 };

  • After the object is created, as follows:

    var person = new Person();

    person.Name = "John";

    person.Age = 10;

There is a lot more to classes that you will see further on. For now, the main ideas are as follows:

  • Classes are blueprints of objects and can have both properties and methods that describe these objects.
  • Objects need to be instantiated so that you can perform operations with them.
  • Classes have one parameter-less constructor by default, but can have many customized ones as required.
  • Object variables are references that contain the memory address of a special memory space allocated to the object inside a dedicated memory section named the heap.

Dates

A date can be represented in C# using the DateTime value type. It is a struct with two static properties called MinValue, which is January 1, 0001 00:00:00, and MaxValue, which is December 31, 9999 11:59:59 P.M. As the names suggest, both these values represent the minimum and maximum dates according to the Gregorian calendar date format. The default value for DateTime objects is MinValue.

It is possible to construct a DateTime variable in various ways. Some of the most common ways are as follows:

  • Assigning the current time as follows:

    var now = DateTime.Now;

This sets the variable to the current date and time on the calling computer, expressed as the local time.

var now = DateTime.UtcNow;

This sets the variable to the current date and time on this computer, expressed as the Coordinated Universal Time (UTC).

  • You can also use constructors for passing days, months, years, hours, minutes, and even seconds and milliseconds.
  • There is also a special property available for DateTime objects called Ticks. It is a measure of the number of 100 nanoseconds elapsed since DateTime.MinValue. Every time you have an object of this type, you can call the Ticks property to get such a value.
  • Another special type for dates is the TimeSpan struct. A TimeSpan object represents a time interval as days, hours, minutes, and seconds. It is useful when fetching intervals between dates. You will now see what this looks like in practice.

Exercise 1.05: Using Date Arithmetic

In this exercise, you will use the TimeSpan method/struct to calculate the difference between your local time and the UTC time. To complete this exercise, perform the following steps:

  1. Open the VS Code integrated terminal and type the following:

    dotnet new console -n Exercise1_05

  2. Open the Program.cs file.
  3. Paste the following inside the Main method and save the file:

    Console.WriteLine("Are the local and utc dates equal? {0}", DateTime.Now.Date == DateTime.UtcNow.Date);

    Console.WriteLine(" If the dates are equal, does it mean that there's no TimeSpan interval between them? {0}",

    (DateTime.Now.Date - DateTime.UtcNow.Date) == TimeSpan.Zero);

    DateTime localTime = DateTime.Now;

    DateTime utcTime = DateTime.UtcNow;

    TimeSpan interval = (localTime - utcTime);

    Console.WriteLine(" Difference between the {0} Time and {1} Time: {2}:{3} hours",

        localTime.Kind.ToString(),

        utcTime.Kind.ToString(),

        interval.Hours,

        interval.Minutes);

    Console.Write(" If we jump two days to the future on {0} we'll be on {1}",

        new DateTime(2020, 12, 31).ToShortDateString(),

        new DateTime(2020, 12, 31).AddDays(2).ToShortDateString());

In the preceding snippet, you first checked whether the current local date and UTC dates were equal. Then you checked for the interval between them, if any, using the TimeSpan method. Next, it printed the difference between the local and UTC time and printed the date two days ahead of the current one (31/12/ 2020, in this case).

  1. Save the file. On the command line, type the following:

    dotnet run --project Exercise1_05

You should see an output like the following:

Are the local and utc dates equal? True

If the dates are equal, does it mean there's no TimeSpan interval between them? True

Difference between the Local Time and Utc Time: 0:0 hours

If we jump two days to the future on 31/12/2020 we'll be on 02/01/2021

Note

You can find the code used for this exercise at https://packt.link/WIScZ.

Note that depending on your time zone, you will likely see different output.

Formatting Dates

It is also possible to format DateTime values to localized strings. That means formatting a DateTime instance according to a special concept within the C# language called a culture, which is a representation of your local time. For instance, dates are represented differently in different countries. Now take a look at the following examples, where dates are outputted in both the format used in France and the format used in the United States:

var frenchDate = new DateTime(2008, 3, 1, 7, 0, 0);

Console.WriteLine(frenchDate.ToString(System.Globalization.CultureInfo.

  CreateSpecificCulture("fr-FR")));

// Displays 01/03/2008 07:00:00

var usDate = new DateTime(2008, 3, 1, 7, 0, 0);

Console.WriteLine(frenchDate.ToString(System.Globalization.CultureInfo.CreateSpecificCulture("en-US")));

// For en-US culture, displays 3/1/2008 7:00:00 AM

It is also possible to explicitly define the format you want the date to be output in, as in the following example, where you pass the yyyyMMddTHH:mm:ss value to say that you want the date to be output as year, then month, then day, then hour, then minutes preceded by a colon, and finally, seconds, also preceded by a colon:

var date1 = new DateTime(2008, 3, 1, 7, 0, 0);

Console.WriteLine(date1.ToString("yyyyMMddTHH:mm:ss"));

The following output gets displayed:

     20080301T07:00:00

Logical Operators and Boolean Expressions

You are already familiar with these. Recall that in the preceding exercise, you did the following comparison:

var now = DateTime.Now.Date == DateTime.UtcNow.Date;

This output assigns the value true to now if the dates are equal. But as you know, they might not necessarily be the same. Therefore, if the dates are different, a false value will be assigned. These two values are the result of such Boolean expressions and are called Boolean values. That is why the now variable has the type of bool.

Boolean expressions are the base for every logical comparison in every program. Based on these comparisons, a computer can execute a certain behavior in a program. Here are some other examples of Boolean expressions and variable assignments:

  • Assigning the result of a comparison that checks whether a is greater than b:

    var basicComparison = a > b;

  • Assigning the result of a comparison that checks whether b is greater than or equal to a:

    bool anotherBasicComparison = b >= a;

  • Checking whether two strings are equal and assigning the result of this comparison to a variable:

    var animal1 = "Leopard";

    var animal2 = "Lion";

    bool areTheseAnimalsSame = animal1 == animal2;

Clearly, the result of the previous comparison would be false and this value will be assigned to the areTheseAnimalsSame variable.

Now that you have learned what Booleans are and how they work, it is time to look at some logical operators you can use to compare Boolean variables and expressions:

  • The && (AND) operator: This operator will perform an equality comparison. It will return true if both are equal and false if they are not. Consider the following example, where you check whether two strings have the length 0:

    bool areTheseStringsWithZeroLength = "".Length == 0 && " ".Length == 0;

    Console.WriteLine(areTheseStringsWithZeroLength);// will return false

  • The || (OR) operator: This operator will check whether either of the values being compared is true. For example, here you are checking whether at least one of the strings has zero length:

    bool isOneOfTheseStringsWithZeroLength = "".Length == 0 || " ".Length == 0;

    Console.WriteLine(isOneOfTheseStringsWithZeroLength); // will return true

  • The ! (NOT) operator: This operator takes a Boolean expression or value and negates it; that is, it returns the opposite value. For example, consider the following example, where you negate the result of a comparison that checks whether one of the strings has zero length:

    bool isOneOfTheseStringsWithZeroLength = "".Length == 0 || " ".Length == 0;

    bool areYouReallySure = !isOneOfTheseStringsWithZeroLength;

    Console.WriteLine(areYouReallySure); // will return false

Using if-else Statements

Up till now, you have learned about types, variables, and operators. Now it is time to go into the mechanisms that help you to use these concepts in real-world problems—that is, decision-making statements.

In C#, if-else statements are some of the most popular choices for implementing branching in code, which means telling the code to follow one path if a condition is satisfied, else follow another path. They are logical statements that evaluate a Boolean expression and continue the program's execution based on this evaluation result.

For example, you can use if-else statements to check whether the password entered satisfies certain criteria (such as having at least six characters and one digit). In the next exercise, you will do exactly that, in a simple console application.

Exercise 1.06: Branching with if-else

In this exercise, you will use if-else statements to write a simple credentials check program. The application should ask the user to enter their username; unless this value is at least six characters in length, the user cannot proceed. Once this condition is met, the user should be asked for a password. The password should also have a minimum of six characters containing at least one digit. Only after both these criteria are met should the program display a success message, such as User successfully registered.

The following steps will help you complete this exercise:

  1. Inside the VS Code integrated terminal, create a new console project called Exercise1_06:

    dotnet new console -n Exercise1_06

  2. Inside the Main method, add the following code to ask the user for a username, and assign the value to a variable:

    Console.WriteLine("Please type a username. It must have at least 6 characters: ");

    var username = Console.ReadLine();

  3. Next, the program needs to check whether the username has more than six characters and if not, write an error message to the console:

    if (username.Length < 6)

    {

    Console.WriteLine($"The username {username} is not valid.");

    }

  4. Now, within an else clause, you will continue the verification and ask the user to type a password. Once the user has entered a password, three points need to be checked. The first condition to check is whether the password has at least six characters and then whether there is at least one number. Then, if either of these conditions fails, the console should display an error message; else, it should display a success message. Add the following code for this:

    else

    {

    Console.WriteLine("Now type a

    password. It must have a length of at least 6 characters and also contain a number.");

    var password = Console.ReadLine();

         

    if (password.Length < 6)

         {

          Console.WriteLine("The password must have at least 6 characters.");

    }

         else if (!password.Any(c => char.IsDigit(c)))

         {

          Console.WriteLine("The password must contain at least one number.");

    }

    else

         {

                 Console.WriteLine("User successfully registered.");

    }

    }

From the preceding snippet, you can see that if the user enters fewer than six characters, an error message is displayed as The password must have at least 6 characters.. If the password doesn't contain a single digit but satisfies the preceding condition, another error message is displayed as The password must contain at least one number..

Notice the logical condition used for this, which is !password.Any(c => char.IsDigit(c)). You will learn more about the => notation in Chapter 2, Building Quality Object-Oriented Code, but for now, you just need to know that this line checks every character in the password and uses the IsDigit function to check whether the character is a digit. This is done for every character, and if no digit is found, the error message is displayed. If all the conditions are met, a success message is displayed as User successfully registered..

  1. Run the program using dotnet run. You should see an output like the following:

    Please type a username. It must have at least 6 characters:

    thekingjames

    Now type a password. It must have at least 6 characters and a number.

    James123!"#

    User successfully registered

    Note

    You can find the code used for this exercise at https://packt.link/3Q7oK.

In this exercise, you worked with if-else branching statements to implement a simple user registration program.

The Ternary Operator

Another simple-to-use, yet effective, decision-making operator is the ternary operator. It allows you to set the value of a variable based on a Boolean comparison. For example, consider the following example:

var gift = person.IsChild() ? "Toy" : "Clothes";

Here, you are using the ? symbol to check whether the Boolean condition placed before it is valid. The compiler runs the IsChild function for the person object. If the method returns true, the first value (before the : symbol) will be assigned to the gift variable. If the method returns false, the second value (after the : symbol) will be assigned to the gift variable.

The ternary operator is simple and makes assignments based on simple Boolean verifications even more concise. You will be using this quite often in your C# journey.

Reference and Value Types

There are two types of variables in C#, namely, reference types and value types. Variables of value types, such as structs, contain the values themselves, as the name suggests. These values are stored in a memory space called the stack. When a variable of such a type is declared, specific memory space is allocated to store this value, as illustrated in the following figure:

Figure 1.2: Memory allocation for a value type variable

Figure 1.2: Memory allocation for a value type variable

Here, the value of the variable, which is 5, is stored in memory at the location 0x100 in the RAM. The built-in value types for C# are bool, byte, char, decimal, double, enum, float, int, long, sbyte, short, struct, uint, ulong, and ushort.

The scenario for reference type variables is different, though. The three main reference types you need to know about in this chapter are string, array, and class. When a new reference type variable is assigned, what is stored in memory is not the value itself, but instead a memory address where the value gets allocated. For example, consider the following diagram:

Figure 1.3: Memory allocation for a reference type variable

Figure 1.3: Memory allocation for a reference type variable

Here, instead of the value of the string variable (Hello), the address where it is allocated (0x100) is stored in memory. For brevity, you will not dive deep into this topic, but it is important to know the following points:

  • When value type variables are passed as parameters or assigned as the value of another variable, the .NET runtime copies the value of the variable to the other object. This means that the original variable is not affected by any changes made in the newer and subsequent variables, as the values were literally copied from one place to another.
  • When reference type variables are passed as parameters or assigned as the value of another variable, .NET passes the heap memory address instead of the value. This means that every subsequent change made in this variable inside a method will be reflected outside.

For instance, consider the following code, which deals with integers. Here, you declare an int variable named a and assign the value 100 to it. Later, you create another int variable named b and assign the value of a to it. Finally, you modify b, to be incremented by 100:

using System;

int a = 100;

Console.WriteLine($"Original value of a: {a}");

int b = a;

Console.WriteLine($"Original value of b: {b}");

b = b + 100;

Console.WriteLine($"Value of a after modifying b: {a}");

Console.WriteLine($"Value of b after modifying b: {b}");

The values of a and b will be displayed in the following output:

Original value of a: 100

Original value of b: 100

Value of a after modifying b: 100

Value of b after modifying b: 200

In this example, the value from a was copied into b. From this point, any other modification you do on b will reflect changes only in b and a will continue to have its original value.

Now, what if you pass reference types as method arguments? Consider the following program. Here, you have a class named Car with two properties—Name and GearType. Inside the program is a method called UpgradeGearType that receives an object of the Car type and changes its GearType to Automatic:

using System;

var car = new Car();

car.Name = "Super Brand New Car";

car.GearType = "Manual";

Console.WriteLine($"This is your current configuration for the car {car.Name}: Gea–Type - {car.GearType}");

UpgradeGearType(car);

Console.WriteLine($"You have upgraded your car {car.Name} for the GearType {car.GearType}");

void UpgradeGearType(Car car)

{

    car.GearType = "Automatic";

}

class Car

{

    public string Name { get; set; }

    public string GearType { get; set; }

}

After you create a Car instance and call the UpgradeGearType() method, the output will be as the follows:

This is your current configuration for the car Super Brand New Car: GearType – Manual

You have upgraded your car Super Brand New Car for the GearType Automatic

Thus, you see that if you pass an object of a reference type (car in this case) as an argument to a method (UpgradeGearType in this example), every change made inside this object is reflected after and outside the method call. This is because reference types refer to a specific location in memory.

Exercise 1.07: Grasping Value and Reference Equality

In this exercise, you will see how equality comparison is different for value types and reference types. Perform the following steps to do so:

  1. In VS Code, open the integrated terminal and type the following:

    dotnet new console -n Exercise1_07

  2. Open the Program.cs file. In the same file, create a struct named GoldenRetriever with a Name property, as follows:

    struct GoldenRetriever

    {

        public string Name { get; set; }

    }

  3. Still in the same file, create one more class named BorderCollie with a similar Name property:

    class BorderCollie

    {

        public string Name { get; set; }

    }

  4. One final class must be created, a class named Bernese, also having the Name property, but with an extra override of the native Equals method:

    class Bernese

    {

        public string Name { get; set; }

        public override bool Equals(object obj)

        {

            if (obj is Bernese borderCollie && obj != null)

            {

                return this.Name == borderCollie.Name;

            }

            return false;

        }

    }

Here, the this keyword is used to refer to the current instance of the borderCollie class.

  1. Finally, in the Program.cs file, you will create some objects for these types. Note that since you are using top-level statements, these declarations should be above the class and the struct declarations:

            var aGolden = new GoldenRetriever() { Name = "Aspen" };

            var anotherGolden = new GoldenRetriever() { Name = "Aspen" };

            var aBorder = new BorderCollie() { Name = "Aspen" };

            var anotherBorder = new BorderCollie() { Name = "Aspen" };

            var aBernese = new Bernese() { Name = "Aspen" };

            var anotherBernese = new Bernese() { Name = "Aspen" };

  2. Now, right after the previous declarations, compare these values using the Equals method and assign the result to some variables:

    var goldenComparison = aGolden.Equals(anotherGolden) ? "These Golden Retrievers have the same name." : "These Goldens have different names.";

    var borderComparison = aBorder.Equals(anotherBorder) ? "These Border Collies have the same name." : "These Border Collies have different names.";

    var berneseComparison = aBernese.Equals(anotherBernese) ? "These Bernese dogs have the same name." : "These Bernese dogs have different names.";

  3. Finally, print the comparison results to the console with the following:

              Console.WriteLine(goldenComparison);

              Console.WriteLine(borderComparison);

              Console.WriteLine(berneseComparison);

  4. Run the program from the command line using dotnet run and you will see the following output:

    These Golden Retrievers have the same name.

    These Border Collies have different names.

    These Bernese dogs have the same name.

    Note

    You can find the code used for this exercise at https://packt.link/xcWN9.

As mentioned earlier, structs are value types. Therefore, when two objects of the same struct are compared with Equals, .NET internally checks all the struct properties. If those properties have equal values, then true is returned. With Golden Retrievers, for instance, if you had a FamilyName property and this property was different between the two objects, the result of the equality comparison would be false.

For classes and all other reference types, the equality comparison is quite different. By default, object reference is checked on equality comparison. If the references are different (and they will be, unless the two variables are assigned to the same object), the equality comparison will return false. This explains the result you see for Border Collies in the example that the references were different for the two instances.

However, there is a method that can be implemented in reference types called Equals. Given two objects, the Equals method can be used for comparison following the logic placed inside the method. That is exactly what happened with the Bernese dogs example.

Default Value Types

Now that you have dealt with value and reference types, you will briefly explore the default value types. In C#, every type has a default value, as specified in the following table:

Figure 1.4: Default value types table

Figure 1.4: Default value types table

These default values can be assigned to a variable using the default keyword. To use this word in a variable declaration, you must explicitly declare the variable type before its name. For example, consider the following snippet, where you are assigning the default value to two int variables:

int a = default;

int b = default;

Both a and b will be assigned the value 0 in this case. Note that it is not possible to use var in this case. This is because, for implicitly declared variables, the compiler needs a value assigned to the variable in order to infer its type. So, the following snippet will lead to an error because no type was set, either through an explicit declaration or by variable assignment:

var a = default;

var b = default;

Enhancing Decision Making with the switch Statement

The switch statement is often used as an alternative to the if-else construct if a single expression is to be tested against three or more conditions, that is, when you want to select one of many code sections to be executed, such as the following:

switch (matchingExpression)

{

  case firstCondition:

    // code section

    break;

  case secondCondition:

    // code section

    break;

  case thirdCondition:

    // code section

    break;

  default:

    // code section

    break;

}

The matching expression should return a value that is of one of the following types: char, string, bool, numbers, enum, and object. This value will then be evaluated within one of the matching case clauses or within the default clause if it does not match any prior clause.

It is important to say that only one switch section in a switch statement will be executed. C# doesn't allow execution to continue from one switch section to the next. However, a switch statement does not know how to stop by itself. You can either use the break keyword if you only wish to execute something without returning or return something if that is the case.

Also, the default keyword on a switch statement is where the execution goes if none of the other options are matched. In the next exercise, you will use a switch statement to create a restaurant menu app.

Exercise 1.08: Using switch to Order Food

In this exercise, you will create a console app that lets the user select from a menu of food items available at a restaurant. The app should display an acknowledgment receipt for the order. You will use the switch statement to implement the logic.

Follow these steps to complete this exercise:

  1. Create a new console project called Exercise1_08.
  2. Now, create an instance of System.Text.StringBuilder. This is a class that helps build strings in many ways. Here, you are building strings line by line so that they can be properly displayed on the console:

    var menuBuilder = new System.Text.StringBuilder();

    menuBuilder.AppendLine("Welcome to the Burger Joint. ");

    menuBuilder.AppendLine(string.Empty);

    menuBuilder.AppendLine("1) Burgers and Fries - 5 USD");

    menuBuilder.AppendLine("2) Cheeseburger - 7 USD");

    menuBuilder.AppendLine("3) Double-cheeseburger - 9 USD");

    menuBuilder.AppendLine("4) Coke - 2 USD");

    menuBuilder.AppendLine(string.Empty);

    menuBuilder.AppendLine("Note that every burger option comes with fries and ketchup!");

  3. Display the menu on the console and ask the user to choose one of the options:

    Console.WriteLine(menuBuilder.ToString());

    Console.WriteLine("Please type one of the following options to order:");

  4. Read the key that the user presses and assign it to a variable with the Console.ReadKey() method. This method works similarly to ReadLine(), which you have used before, with the difference that it reads the key that is immediately pressed after calling the method. Add the following code for this:

    var option = Console.ReadKey();

  5. Now it is time to use the switch statement. Use option.KeyChar.ToString() as the matching expression of the switch clause here. Keys 1, 2, 3, and 4 should result in orders accepted for burgers, cheeseburgers, double cheeseburgers, and Coke, respectively:

    switch (option.KeyChar.ToString())

    {

        case "1":

            {

                Console.WriteLine(" Alright, some burgers on the go. Please pay the cashier.");

                break;

            }

        case "2":

            {

                Console.WriteLine(" Thank you for ordering cheeseburgers. Please pay the cashier.");

                break;

            }

        case "3":

            {

                Console.WriteLine(" Thank you for ordering double cheeseburgers, hope you enjoy them. Please pay the cashier!");

Any other input, however, should be considered invalid and a message gets displayed, letting you know you have selected an invalid option:

            break;

        }

    case "4":

        {

            Console.WriteLine(" Thank you for ordering Coke. Please pay the cashier.");

            break;

        }

    default:

        {

            Console.WriteLine(" Sorry, you chose an invalid option.");

            break;

        }

}

  1. Finally, run the program with dotnet run --project Exercise1_08 and interact with the console to see the possible outputs. For example, if you type 1, you should see an output like the following:

    Welcome to the Burger Joint.

    1) Burgers and Fries – 5 USD

    2) Cheeseburger – 7 USD

    3) Double-cheeseburger – 9 USD

    4) Coke – 2 USD

    Note that every burger option comes with fries and ketchup!

    Please type one of the follow options to order:

    1

    Alright, some burgers on the go! Please pay on the following cashier!

    Note

    You can find the code used for this exercise at https://packt.link/x1Mvn.

Similarly, you should get the output for the other options as well. You have learned about branching statements in C#. There is another type of statement that you will use often while programming using C#, called iteration statements. The next section covers this topic in detail.

Iteration Statements

Iteration statements, also called loops, are types of statements that are useful in the real world, as you often need to continuously repeat some logical execution in your applications while or until some condition is met, such as operating with a number that must be incremented until a certain value. C# offers numerous ways of implementing such iterations, and in this section, you will examine each of these in detail.

while

The first iteration statement you will consider is the while statement. This statement allows a C# program to execute a set of instructions while a certain Boolean expression is evaluated to be true. It has one of the most basic structures. Consider the following snippet:

int i = 0;

while (i < 10)

{

Console.WriteLine(i);

i = i +1;

}

The preceding snippet shows how you can use the while statement. Note that the while keyword is followed by a pair of brackets enclosing a logical condition; in this case, the condition is that the value of i must be less than 10. The code written inside the curly braces will be executed until this condition is true.

Thus, the preceding code will print the value of i, starting with 0, up to 10. This is fairly simplistic code; in the next exercise, you will use the while statement for something a little more complex, such as checking whether a number entered by you is a prime number.

Exercise 1.09: Checking Whether a Number is Prime with a while Loop

In this exercise, you will use a while loop to check whether a number you enter is prime. To do so, the while loop will check whether the counter is less than or equal to the integer result of the division of the number by 2. When this condition is satisfied, you check whether the remainder of the division of the number by the counter is 0. If not, you increment the counter and continue until the loop condition is not met. If it is met, it means the number is not false and the loop can stop.

Perform the following steps to complete this exercise:

  1. Inside the VS Code integrated terminal, create a new console project called Exercise1_09.
  2. Inside the Program.cs file, create the following method, which will perform the logic you introduced at the beginning of the exercise:

    static bool IsPrime(int number)

    {

    if (number ==0 || number ==1) return false;

    bool isPrime = true;

    int counter = 2;

    while (counter <= Math.Sqrt(number))

         {

          if (number % counter == 0)

               {

                isPrime = false;

                    break;

    }

    counter++;

    }

         return isPrime;

    }

  3. Now, input a number, so you can check whether it is prime:

    Console.Write("Enter a number to check whether it is Prime: ");

    var input = int.Parse(Console.ReadLine());

  4. Now, check whether the number is prime and print the result:

    Console.WriteLine($"{input} is prime? {IsPrime(input)}.");

  5. Finally, on the VS Code integrated terminal, call dotnet run --project Exercise1_09 and interact with the program. For example, try entering 29 as an input:

    Enter a number to check whether it is Prime:

    29

    29 is prime? True

As expected, the result for 29 is true since it is a prime number.

Note

You can find the code used for this exercise at https://packt.link/5oNg5.

The preceding exercise aimed to show you the simple structure of a while loop with some more complex logic. It checks a number (named input) and prints whether it is a prime number. Here, you have seen the break keyword used again to stop program execution. Now proceed to learn about jump statements.

Jump Statements

There are some other important keywords used within loops that are worth mentioning as well. These keywords are called jump statements and are used to transfer program executions to another part. For instance, you could rewrite the IsPrime method as follows:

static bool IsPrimeWithContinue(int number)

        {

        if (number == 0 || number ==1) return false;

            bool isPrime = true;

            int counter = 2;

            while (counter <= Math.Sqrt(number))

            {

                if (number % counter != 0)

                {

                    counter++;

                    continue;

                }

                isPrime = false;

                break;

            }

            return isPrime;

        }

Here, you have inverted the logical check. Instead of checking whether the remainder is zero and then breaking the program execution, you have checked that the remainder is not zero and, if so, have used the continue statement to pass the execution to the next iteration.

Now look at how you can rewrite this using another special keyword, goto:

static bool IsPrimeWithGoTo(int number)

        {

        if (number == 0 || number ==1) return false;

bool isPrime = true;

            int counter = 2;

            while (counter <= Math.Sqrt(number))

            {

                if (number % counter == 0)

                {

                    isPrime = false;

                    goto isNotAPrime; 

                }

                counter++;

            }

            isNotAPrime:

            return isPrime;

        }

The goto keyword can be used to jump from one part of the code to another one defined by what is called a label. In this case, the label was named isNotAPrime. Finally, take a look at one last way of writing this logic:

static bool IsPrimeWithReturn(int number)

        {

        if (number == 0 || number ==1) return false;

            int counter = 2;

            while (counter <= Math.Sqrt(number))

            {

                if (number % counter == 0)

                {

                    return false;

                }

                counter ++;

            }

            return true;

        }

Now, instead of using break or continue to stop the program execution, you simply use return to break the loop execution since the result that you were looking for was already found.

do-while

The do-while loop is like the previous one, but with one subtle difference: it executes the logic at least once, while a simple while statement may never be executed if the condition is not met at the first execution. It has the following structure:

int t = 0;

do

{

    Console.WriteLine(t);

    t++;

} while (t < 5);

In this example, you write the value of t, starting from 0, and keep incrementing it while it is smaller than 5. Before jumping into the next type of loop, learn about a new concept called arrays.

Arrays

An array is a data structure used to store many objects of the same type. For instance, the following example is a variable declared as an array of integer numbers:

int[] numbers = { 1, 2, 3, 4, 5 };

The first important thing to note about arrays is that they have a fixed capacity. This means that an array will have the length defined at the time of its creation and this length cannot change. The length can be determined in various ways. In the preceding example, the length is inferred by counting the number of objects in the array. However, another way of creating an array is like this:

var numbers = new int[5];

Here, you are creating an array that has the capacity of 5 integers, but you do not specify any value for the array elements. When an array of any data type is created without adding elements to it, the default values for that value type are set for each position of the array. For example, consider the following figure:

Figure 1.5: Value type array with no index assigned

Figure 1.5: Value type array with no index assigned

The preceding figure shows that when you create an integer array of five elements, without assigning a value to any element, the array is automatically filled with the default value at every position. In this case, the default value is 0. Now consider the following figure:

Figure 1.6: Reference type array with fixed size and only one index assigned

Figure 1.6: Reference type array with fixed size and only one index assigned

In the preceding example, you have created an array of five objects and assigned the "Hello" string value to the element at index 1. The other positions of the array are automatically assigned the default value for objects, which is null.

Finally, it is worth noting that all arrays have indexes, which refers to the positions of the individual array elements. The first position will always have an index 0. Thus, the positions in an array of size n can be specified from index 0 to n-1. Therefore, if you call numbers[2], this means that you are trying to access the element in position 2 inside the numbers array.

for Loops

A for loop executes a set of instructions while a Boolean expression matches a specified condition. Just like while loops, jump statements can be used to stop a loop execution. It has the following structure:

for (initializer; condition; iterator)

{

[statements]

}

The initializer statement is executed before the loop starts. It is used to declare and assign a local variable that will be used only inside the scope of the loop.

But in more complex scenarios, it can be used to combine other statement expressions as well. The condition specifies a Boolean condition that indicates when the loop should either continue or exit. The iterator is usually used to increment or decrement the variable created in the initializer section. Take the following example, where a for loop is used to print the elements of an integer array:

int[] array = { 1, 2, 3, 4, 5 };

for (int j = 0; j < array.Length - 1; j++)

{

Console.WriteLine(array[j]);

}

In this example, an initializer variable, j, has been created that is assigned 0 initially. The for loop will keep executing while j is smaller than the array length minus 1 (remember that indexes always start at 0). After each iteration, the value of j is incremented by 1. In this way, the for loop goes through the entire array and performs the given action, that is, printing the value of the current array element.

C# also allows the usage of nested loops, that is, a loop within a loop, as you will see in the next exercise.

Exercise 1.10: Ordering an Array Using Bubble Sort

In this exercise, you will execute one of the simplest sorting algorithms. Bubble sort consists of going through every pair of elements inside an array and swapping them if they are unordered. In the end, the expectation is to have an array ordered in ascending order. You will use nested for loops to implement this algorithm.

To begin with, the array to be sorted should be passed as a parameter to this method. For each element of this array, if the current element is greater than the next, their positions should be swapped. This swap occurs by storing the value of the next element in a temporary variable, assigning the value of the current element to the next element, and finally, setting the value of the current element with the temporary value stored. Once the first element is compared to all others, a comparison starts for the second element and so on, till finally, the array is sorted.

The following steps will help you complete this exercise:

  1. Create a new console project using the following command:

    dotnet new console -n Exercise1_10

  2. Inside the Program.cs file, create the method to implement the sorting algorithm. Add the following code:

    static int[] BubbleSort(int[] array)

    {

        int temp;

        // Iterate over the array

        for (int j = 0; j < array.Length - 1; j++)

        {

            // If the last j elements are already ordered, skip them

            for (int i = 0; i < array.Length - j - 1; i++)

            {

                if (array[i] > array[i + 1])

                {

                    temp = array[i + 1];

                    array[i + 1] = array[i];

                    array[i] = temp;

                }

            }

        }

        return array;

    }

  3. Now create an array with some numbers, as follows:

    int[] randomNumbers = { 123, 22, 53, 91, 787, 0, -23, 5 };

  4. Call the BubbleSort method, passing the array as an argument, and assign the result to a variable, as follows:

    int[] sortedArray = BubbleSort(randomNumbers);

  5. Finally, you need to print the message that the array was sorted. To do so, iterate over it, printing the array elements:

    Console.WriteLine("Sorted:");

    for (int i = 0; i < sortedArray.Length; i++)

    {

        Console.Write(sortedArray[i] + " ");

    }

  6. Run the program with the dotnet run --project Exercise1_10 command. You should see the following output on your screen:

    Sorted:

    -23 0 5 22 53 91 123 787

    Note

    You can find the code used for this exercise at https://packt.link/cJs8y.

In this exercise, you used the two concepts learned in the last two sections: arrays and for loops. You manipulated arrays, accessing their values through indexes, and used for loops to move through these indexes.

There is another way to go through every element of an array or group in C#, called foreach statements. You will explore this in the following section.

foreach Statements

A foreach statement executes a set of instructions for each element of a collection. Just like a for loop, the break, continue, goto, and return keywords can also be used with foreach statements. Consider the following example, in which you iterate over every element of an array and write it to the console as the output:

var items = new int[] { 1, 2, 3, 4, 5 };

foreach (int element in items)

{

Console.WriteLine(element);

}

The preceding snippet prints the numbers from 1 to 5 to the console. You can use foreach statements with much more than arrays; they can also be used with lists, collections, and spans, which are other data structures that will be covered in later chapters.

File Handling

So far, you have been creating programs that interact mostly with CPU and memory. This section will focus on I/O operations, that is, input and output operations, on the physical disk. A great example of this type of operation is file handling.

C# has several classes that help you perform I/O operations. Some of these are as follows:

  • File: This class provides methods for the manipulation of files, that is, reading, writing, creating, deleting, copying, and moving files on the disk.
  • Directory: Like the File class, this class includes methods to create, move, and enumerate directories and subdirectories on the disk.
  • Path: This provides utilities to deal with absolute and relative paths of files and directories on the disk. A relative path is always related to some path inside the current directory where the application is being executed, and an absolute path refers to an absolute location inside the hard drive.
  • DriveInfo: This provides information about a disk drive, such as Name, DriveType, VolumeLabel, and DriveFormat.

You already know that files are mostly some sets of data located somewhere in a hard drive that can be opened for reading or writing by some program. When you open a file in a C# application, your program reads the file as a sequence of bytes through a communication channel. This communication channel is called a stream. Streams can be of two types:

  • The input streams are used for reading operations.
  • The output streams are used for writing operations.

The Stream class is an abstract class in C# that enables common operations regarding this byte flow. For file handling on a hard disk, you will use the FileStream class, designed specifically for this purpose. The following are two important properties of this class: FileAccess and FileMode.

FileAccess

This is an enum that provides you with options to choose a level of access when opening a specified file:

  • Read: This opens a file in read-only mode.
  • ReadWrite: This opens a file in read and write mode.
  • Write: This opens a file in write-only mode. This is rarely used, as you usually do some reading along with the writing.

FileMode

This is an enum that specifies the operations that can be performed on a file. It should be used along with the access mode as some modes only work with some levels of access. Take a look at the options, as follows:

  • Append: Use this when you want to add content at the end of the file. If the file does not exist, a new one will be created. For this operation, the file must have write permission; otherwise, any attempt to read fails and throws a NotSupportedException exception. Exceptions are an important concept that will be covered later in this chapter.
  • Create: Use this to create a new file or overwrite an existing one. For this option, too, write permission is required. In Windows, if the file exists but is hidden, an UnauthorizedAccessException exception is thrown.
  • CreateNew: This is like Create but is used to create new files and also requires write permission. However, if the file already exists, an IOException exception is thrown.
  • Open: As the name suggests, this mode is used to open a file. The file must have read or read and write permissions. If the file does not exist, a FileNotFoundException exception is thrown.
  • OpenOrCreate: This is like Open, except it creates a new file if it does not already exist.

Exercise 1.11: Reading Content from Text Files

In this exercise, you will read text from a Comma-Separated Values (CSV) file. CSV files simply contain data represented by strings and separated either by colons or semicolons.

Perform the following steps to complete this exercise:

  1. Open Command Prompt and type the following:

    dotnet new console -n Exercise1_11

  2. At the Exercise1_11 project folder location in your computer, create a file named products.csv and paste the following content inside it:

    Model;Memory;Storage;USB Ports;Screen;Condition;Price USD

    Macbook Pro Mid 2012;8GB;500GB HDD;USB 2.0x2;13" screen;Refurbished;400

    Macbook Pro Mid 2014;8GB;512GB SSD;USB 3.0x3;15" screen;Refurbished;750

    Macbook Pro Late 2019;16GB;512GB SSD;USB 3.0x3;15" screen;Refurbished;1250

  3. Open the Program.cs file and replace its contents with the following:

    using System;

    using System.IO;

    using System.Threading.Tasks;

    namespace Exercise1_11

    {

        public class Program

        {

            public static async Task Main()

            {

            using (var fileStream = new FileStream("products.csv", FileMode.Open, FileAccess.Read))

            {

                using (var reader = new StreamReader(fileStream))

                {

                    var content = await reader.ReadToEndAsync();

                    var lines = content.Split(Environment.NewLine);

                    foreach (var line in lines)

                    {

                        Console.WriteLine(line);

                    }

                }

            }

            }

        }

    }

  4. Call dotnet run in Command Prompt and you will get an output that is the same as the contents of the CSV file you have created.

    Note

    You can find the code used for this exercise at https://packt.link/5flid.

This exercise has some pretty interesting outcomes, which you are going to learn step by step. First, you opened a file using the FileStream class. This allows you to start streaming bytes from a file with two special properties, namely, FileMode and FileAccess. It will return a stream of bytes with the file contents. However, to read this content as text, you need to use the StreamReader class. This class enables you to read these bytes as text characters.

Notice also that your Main method changed from void to async Task. Additionally, the await keyword has been used, which is used for asynchronous operations. You will learn more about these topics in upcoming chapters. For now, you only need to know that an async operation is something that does not block the program execution. This means that you can output lines as they are being read; that is, you do not have to wait for all of them to be read.

In the next section, learn about the special keyword that handles files, databases, and network connections.

Disposable Objects

Another special thing about the preceding exercise was the using keyword. It is a keyword used to clean up unmanaged resources from memory. These resources are special objects that handle some operational system resources, such as files, databases, and network connections. They are called special because they do what is called I/O operations; that is, they interact with the real resources of the machine, such as network and hard drives, not just with memory spaces.

The memory used by objects in C# is handled by something called the garbage collector. By default, C# handles the memory space in the stack and the heap. The only types of objects that do not perform this cleanup are called unmanaged objects.

Cleaning these objects from memory means that the resources will be free to be used by another process in the computer. That means a file can be handled by another one, a database connection is free to be used again by a connection pool, and so on. Those types of resources are called disposable resources. Every time you deal with a disposable resource, you can use the using keyword when creating an object. Then, the compiler knows that when the using statement closes, it can automatically free these resources.

Exercise 1.12: Writing to a Text File

In this exercise, you will write some text into a CSV file, again using the FileStream class.

Follow these steps to complete this exercise:

  1. Open the VS Code integrated terminal and type the following:

    dotnet new console -n Exercise1_12

  2. At a preferred location on your computer, copy the products.csv file from the previous exercise and paste it into this exercise's folder.
  3. In Program.cs, create a method named ReadFile that will receive a FileStream file and iterate over the file lines to output the result to the console:

    static async Task ReadFile(FileStream fileStream)

        {

            using (var reader = new StreamReader(fileStream))

            {

                var content = await reader.ReadToEndAsync();

                var lines = content.Split(Environment.NewLine);

                foreach (var line in lines)

                {

                    Console.WriteLine(line);

                }

            }

        }

  4. Now, in your program, open the products.csv file with StreamWriter and add some more information to it, as follows:

            using (var file = new StreamWriter("products.csv", append: true))

            {

                file.Write(" One more macbook without details.");

            }

  5. Finally, read the contents of the file after modification:

    using (var fileStream = new FileStream("products.csv", FileMode.Open,

                FileAccess.Read))

            {

                await ReadFile(fileStream);

            }

  6. Call dotnet run --project Exercise1_12 in the VS Code integrated terminal and you will be able to see the contents of the CSV file you just created, in addition to the line you just appended:

    Model;Memory;Storage;USB Ports;Screen;Condition;Price USD

    Macbook Pro Mid 2012;8GB;500GB HDD;USB 2.0x2;13" screen;Refurbished;400

    Macbook Pro Mid 2014;8GB;512GB SSD;USB 3.0x3;15" screen;Refurbished;750

    Macbook Pro Late 2019;16GB;512GB SSD;USB 3.0x3;15" screen;Refurbished;1250

    One more macbook without details.

Note that for each run, the program will append a new line, so you will see more lines being added.

Note

You can find the code used for this exercise at https://packt.link/dUk2z.

Sometimes your program will fail to execute at some point and may not provide an output. Such an instance is called an exception error. The next section details all about such an error.

Exceptions

Exceptions indicate that a program has failed to execute at some point for some reason and can be raised by either the code itself or the .NET runtime. Usually, an exception is a severe failure and can even terminate your program's execution. Fortunately, C# provides a special way of handling exceptions, which is try/catch blocks:

try

{

// some logic that might throw an exception

}

catch

{

// error handling

}

Inside the try clause, you call the code that might throw an exception, and inside the catch clause, you can treat the exception that was raised. For instance, consider the following example:

double Divide(int a, int b) => a/b;

This method takes two integers and returns the result of a division between them. However, what will happen if b is 0? In such a case, the runtime will throw System.DivideByZeroException, indicating that it is not possible to execute the division. How could you handle this exception in a real-world program? You will explore this in the next exercise.

Exercise 1.13: Handling Invalid User Inputs with try/catch

In this exercise, you will create a console app that takes two inputs from you, divides the first number by the second one, and outputs the result. If you enter an invalid character, the app should throw an exception, and all of this should be handled inside the program logic.

Perform the following steps to complete this exercise:

  1. Inside the VS Code integrated terminal, create a new console app called Exercise1_13.
  2. Create the following method inside the Program.cs file:

    static double Divide(int a, int b)

    {

        return a / b;

    }

  3. Now, create a Boolean variable to indicate whether the division was properly executed. Assign false to it as its initial value:

    bool divisionExecuted = false;

  4. Write a while loop that will check whether the division happened successfully. If it did, the program should terminate. If not, the program should prompt you to input valid data and perform the division again. Add the following code to do this:

    while (!divisionExecuted)

    {

        try

        {

            Console.WriteLine("Please input a number");

            var a = int.Parse(Console.ReadLine());

            Console.WriteLine("Please input another number");

            var b = int.Parse(Console.ReadLine());

            var result = Divide(a, b);

            Console.WriteLine($"Result: {result}");

            divisionExecuted = true;

        }

        catch (System.FormatException)

        {

            Console.WriteLine("You did not input a number. Let's start again ... ");

            continue;

        }

        catch (System.DivideByZeroException)

        {

            Console.WriteLine("Tried to divide by zero. Let's start again ... ");

            continue;

        }

    }

  5. Finally, execute the program using the dotnet run command and interact with the console. Try to insert strings instead of numbers and see what output you get. Look at the following output as an example:

    Please input a number

    5

    Please input another number

    0

    Tried to divide by zero. Let's start again …

    Please input a number

    5

    Please input another number

    s

    You did not input a number. Let's start again …

    Please input a number

    5

    Please input another number

    1

    Result: 5

    Note

    You can find the code used for this exercise at https://packt.link/EVsrJ.

In this exercise, you handled two types of exceptions that are as follows:

  • The int.Parse(string str) method throws System.FormatException if it is not possible to convert the string variable into an integer.
  • The double Divide(int a, int b) method throws System.DivideByZeroException if b is 0.

Now that you have seen how exceptions are handled, it is important to note a rule of thumb that will help you in your C# journey, which is that you should only catch what you can or what you need to handle. There are only a few situations where exception handling is really needed, as follows:

  • When you want to mask an exception, that is, catch it and pretend that nothing happened. This is known as exception suppression. That should take place when the exception that is thrown does not impact the flow of your program.
  • When you want to control your program's execution flow to perform some alternate actions, as you did in the preceding exercise
  • When you want to catch a type of exception to throw it as another type. For instance, when communicating with your web API, you might see an exception of type HttpException that indicates that the destination is unreachable. You could make use of a custom exception here, such as IntegrationException, to indicate more clearly that it happened in a part of your application that performs some integrations with external APIs.

The throw keyword can also be used to intentionally stop the program execution flow in certain cases. For example, consider that you are creating a Person object and that the Name property should not be null at the time of creation. You can enforce on this class a contract that says: if these parameters are not correctly provided, it cannot be used. Typically, you would do so by throwing System.ArgumentException or System.ArgumentNullException, as in the following snippet, which uses ArgumentNullException to do so:

class Person

{

Person(string name)

     {

if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));

Name = name;

     }

    String Name { get ; set; }

}

Here, if the value of the name argument is null or if you only enter space characters, ArgumentNullException is thrown, and the program does not execute successfully. The null/white space condition is checked with the help of the IsNullOrWhiteSpace function, which can be used for string variables.

Now it's time to practice all that you learned in the previous sections through an activity.

Activity 1.01: Creating a Guessing Game

To complete this activity, you need to create a guessing game using the concepts you have learned about and practiced so far in this chapter. In this game, first, a random number from one to 10 must be generated, not to be output to the console. The console should then prompt the user to input a number and then guess which random number has been generated, and the user should get a maximum of five chances.

Upon every incorrect input, a warning message should be displayed, letting the user know how many chances they have left, and if all five chances are exhausted with incorrect guesses, the program terminates. However, once the user guesses correctly, a success message should be displayed, before the program terminates.

The following steps will help you complete this activity:

  1. Create a variable called numberToBeGuessed that is assigned to a random number within C#. You can use the following snippet to do so:

    new Random().Next(0, 10)

This generates a random number for you, between 0 and 10. You could replace 10 with a higher number if you wanted to make the game a little more difficult, or with a smaller number to make it easier, but for this activity, you will use 10 as the maximum value.

  1. Create a variable called remainingChances that will store the remaining number of chances that the user has.
  2. Create a numberFound variable and assign a false value to it.
  3. Now, create a while loop that will execute while there are still some chances remaining. Within this loop, add code to output the number of chances remaining, until the correct guess is made. Then, create a variable called number that will receive the parsed integer for the user input. Finally, write code to check whether the number variable is the correct guess, and assign the value true to the numberFound variable if so. If not, the number of remaining chances should be reduced by 1.
  4. Finally, add code to inform users whether they have guessed the number correctly. You can output something such as Congrats! You've guessed the number with {remainingChanges} chances left! if they guessed correctly. If they ran out of chances, output You're out of chances. The number was {numberToBeGuessed}..

    Note

    The solution to this activity can be found at https://packt.link/qclbF.

Summary

This chapter gave you an overview of the fundamentals of C# and what it looks like to write programs with it. You explored everything from the variable declaration, data types, and basic arithmetic and logical operators to file and exception handling. You also explored how C# allocates memory while dealing with value and reference types.

In the exercises and activities in this chapter, you were able to solve some real-world problems and think of solutions that can be implemented with this language and its resources. You learned how to prompt for user inputs in console apps, how to handle files within a system, and finally, how to deal with unexpected inputs through exception handling.

The next chapter will cover the essentials of Object-oriented programming, diving deeper into the concept of classes and objects. You will also learn about the importance of writing clean, concise code that is easy to maintain, and the principles you can follow for writing such code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset