Book Image

Learn C# Programming

By : Marius Bancila, Raffaele Rialdi, Ankit Sharma
5 (1)
Book Image

Learn C# Programming

5 (1)
By: Marius Bancila, Raffaele Rialdi, Ankit Sharma

Overview of this book

The C# programming language is often developers’ primary choice for creating a wide range of applications for desktop, cloud, and mobile. In nearly two decades of its existence, C# has evolved from a general-purpose, object-oriented language to a multi-paradigm language with impressive features. This book will take you through C# from the ground up in a step-by-step manner. You'll start with the building blocks of C#, which include basic data types, variables, strings, arrays, operators, control statements, and loops. Once comfortable with the basics, you'll then progress to learning object-oriented programming concepts such as classes and structures, objects, interfaces, and abstraction. Generics, functional programming, dynamic, and asynchronous programming are covered in detail. This book also takes you through regular expressions, reflection, memory management, pattern matching, exceptions, and many other advanced topics. As you advance, you'll explore the .NET Core 3 framework and learn how to use the dotnet command-line interface (CLI), consume NuGet packages, develop for Linux, and migrate apps built with .NET Framework. Finally, you'll understand how to run unit tests with the Microsoft unit testing frameworks available in Visual Studio. By the end of this book, you’ll be well-versed with the essentials of the C# language and be ready to start creating apps with it.
Table of Contents (20 chapters)

Reference types and value types

The data types in C# are divided into value types and reference types. There are several important differences between these two, such as copy semantics. We will look at these in detail in the following sections.

Value types

A variable of a value type contains the value directly. When a value type variable is assigned from another, the stored value is copied. The primitive data types we have seen earlier are all value types. All user-defined types declared as structures (with the struct keyword) are value types. Although all types are implicitly derived from the object, type value types do not support explicit inheritance, which is a topic discussed in Chapter 4, Understanding the Various User-Defined Types.

Let's see an example here:

int a = 20;
DateTime dt = new DateTime(2019, 12, 25);

Value types are typically stored on the stack in memory, although this is an implementation detail and not a characteristic of value types. If you assign the value of a value type to another variable, then the value is copied to the new variable and changing one variable will not affect the other:

int a = 20;
int b = a;  // b is 20
a = 42;     // a is 42, b is 20

In the preceding example, the value of a is initialized to 20 and then assigned to the variable b. At this point, both variables contain the same value. However, after assigning the value 42 to the a variable, the value of b remains unchanged. This is shown, conceptually, in the following diagram:

Figure 2.1 – A conceptual representation of the changes in the stack during the execution of the previous code

Figure 2.1 – A conceptual representation of the changes in the stack during the execution of the previous code

Here, you can see that, initially, a storage location corresponding to the a integer was allocated on the stack and had the value 20. Then, a second storage location was allocated and the value from the first was copied to it. Then, we changed the value of the a variable and therefore, the value available in the first storage location. The second one was left untouched.

Reference types

A variable of a reference type does not contain the value directly but a reference to a memory location where the actual value is stored. The built-in data types object and string are reference types. Arrays, interfaces, delegates, and any user-defined type defined as a class are also called reference types. The following example shows several variables of different reference types:

int[]  a = new int[10];
string s = "sample";
object o = new List<int>();

Reference types are stored on the heap. Variables of a reference type can be assigned the null value that indicates that the variable does not store a reference to an instance of an object. When trying to use a variable assigned the null value the result is a runtime exception. When a variable of a reference type is assigned a value, the reference to the actual memory location of the object is copied and not the value of the object itself.

In the following example, a1 is an array of two integers. The reference to the array is copied to the a2 variable. When the content of the array changes, the changes are visible both through a1 and a2, since both these variables refer to the same array:

int[] a1 = new int[] { 42, 43 };
int[] a2 = a1;   // a2 is { 42, 43 }
a1[0] = 0;       // a1 is { 0, 43 }, a2 is { 0, 43 }

This example is explained conceptually in the following diagram:

Figure 2.2 – The conceptual representation of the stack and the heap during the execution of the preceding snippet

Figure 2.2 – The conceptual representation of the stack and the heap during the execution of the preceding snippet

You can see in this diagram that a1 and a2 are variables on the stack pointing to the same array of integers allocated on the heap. When the first element of the array is changed through the a1 variable, the changes are automatically visible to the a2 variable because a1 and a2 refer to the same object.

Although the string type is a reference type, it appears to behave differently. Take the following example:

string s1 = "help";
string s2 = s1;     // s2 is "help"
s1 = "demo";        // s1 is "demo", s2 is "help"

Here, s1 is initialized with the "help" literal and then the reference to the actual array heap object is copied to the s2 variable. At this point, they both refer to the "help" string. However, s1 is later assigned a new string, "demo". At this point, s2 will continue to refer to the "help" string. The reason for this is that strings are immutable. That means when you modify a string object, a new string is created, and the variable will receive the reference to the new string object. Any other variables referring to the old string will continue to do so.

Boxing and unboxing

We briefly mentioned boxing and unboxing earlier in this chapter when we talked about the object type. Boxing is the process of storing a value type inside an object, and unboxing is the opposite operation of converting the value of an object to a value type. Let's understand this with the help of an example:

int a = 42;
object o = a;   // boxing
o = 43;
int b = (int)o; // unboxing
Console.WriteLine(x);  // 42
Console.WriteLine(y);  // 43

In the preceding code, a is a variable of the type integer that is initialized with the value 42. Being a value type, the integer value 42 is stored on the stack. On the other hand, o is a variable of type object. This is a reference type. That means it only contains a reference to a heap memory location where the actual object is stored. So, when a is assigned to o, the process called boxing occurs.

During the boxing process an object is allocated on the heap, the value of a (which is 42) is copied to it, and then a reference to this object is assigned to the o variable. When we later assigned the value 43 to o, only the boxed object changes and not a. Lastly, we copy the value of the object referred by o to a new variable called b. This will have the value 43 and, being an int, is also stored on the stack.

The process described here is shown graphically in the following diagram:

Figure 2.3 – Conceptual representation of the stack showing the boxing and unboxing process described previously

Figure 2.3 – Conceptual representation of the stack showing the boxing and unboxing process described previously

Now that you understand the difference between value and reference types, let's look at the topic of nullable types.