All applications process data. Data comes in, data is processed, and data goes out.
Data usually comes into our program from files, databases, or user input. Data can be put temporarily in variables that will be stored in the memory of the running program. When the program ends, the data in memory is lost. Data is usually output to files and databases, or to the screen or a printer.
When using variables, you should think about, first, how much space it takes in memory, and, second, how fast it can be processed.
We control this by picking an appropriate type. You can think of simple common types such as int
and double
as being different size storage boxes. A smaller box would take less memory but may not be as fast at being processed. Some of these boxes may be stacked close by, and some may be thrown into a big heap further away.
There are naming conventions for variables, and it is good practice to follow them, as shown in the following table:
Naming convention |
Examples |
Use when naming |
Camel case |
|
Local variables and private members. |
Pascal/title case |
|
Type names and non-private members. |
Good Practice
Following a consistent set of naming conventions will enable your code to be easily understood by other developers (and yourself in the future!) Naming Guidelines: https://msdn.microsoft.com/en-us/library/ms229002(v=vs.110).aspx
The following code block shows an example of declaring and initializing a local variable by assigning a value to it. Note that you can output the name of a variable using a keyword introduced in C# 6, that is, nameof
:
// let the height variable become equal to the value 1.88 double heightInMetres = 1.88; Console.WriteLine($"The variable {nameof(heightInMetres)} has the value {heightInMetres}.");
When you assign to a variable, you often assign a literal value. A literal is notation that represents a fixed value. Data types have different notations for their literal values.
For text, a single letter, such as A
, is stored as a char
type and is assigned using single quotes around the literal value:
char letter = 'A';
For text, multiple letters, such as Bob
, are stored as a string
type and are assigned using double quotes around the literal value:
string name = "Bob";
Numbers are data that we want to perform an arithmetic calculation on, for example, multiplying.
A telephone number is not a number. To decide whether a variable should be stored as a number or not, ask yourself whether you need to multiply two telephone numbers together or whether the number includes special characters such as (414)-555-1234. In these cases, the number is a sequence of characters, so it should be stored as a string.
Numbers can be natural numbers, such as 42, used for counting (also called whole numbers); they can also be negative numbers, such as -42 (called integers); or, they can be real numbers, such as 3.9 (with a fractional part), which are called single or double-precision floating point numbers in computing.
int myIntegerNumber = 23; double myRealNumber = 2.3;
You might know that computers store everything as bits. A bit is either 0 or 1. This is called a binary number system. Humans use a decimal number system.
The following table shows how computers store the number 10. Note the 1 bits in the 8 and the 2 columns; 8 + 2 = 10:
128 |
64 |
32 |
16 |
8 |
4 |
2 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
So, 10 in decimal is 00001010 in binary.
Two of the improvements in C# 7 are the use of the underscore character (_
) as a digit separator and support for binary literals.
You can insert underscores anywhere into a number literal, including decimal, binary, or hexadecimal notation to improve legibility. For example, you could write the value for one million in decimal notation (Base 10) as: 1_000_000.
To use binary notation (Base 2), using only 1s and 0s, start the number literal with 0b. To use hexadecimal notation (Base 16), using 0 to 9 and A to F, start the number literal with 0x, as shown in the following code:
int decimalNotation = 2_000_000; // 2 million int binaryNotation = 0b_0001_1110_1000_0100_1000_0000; // 2 million int hexadecimalNotation = 0x_001E_8480; // 2 million
Computers can always exactly represent integers (positive and negative whole numbers) using the int
type or one of its sibling types such as short
.
Computers cannot always exactly represent floating point numbers. The float
and double
types store real numbers using single and double precision floating points.
The following table shows how a computer stores the number 12.75. Note the 1 bits in the 8, 4, ½, and ¼ columns.
8 + 4 + ½ + ¼ = 12¾ = 12.75.
128 |
64 |
32 |
16 |
8 |
4 |
2 |
1 |
. |
½ |
¼ |
1/8 |
1/16 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
. |
1 |
1 |
0 |
0 |
So, 12.75 in decimal is 00001100.1100 in binary.
As you can see, the number 12.75 can be exactly represented using bits. However, some numbers can't, as you will see shortly.
In Visual Studio 2017, click on File | Add | New Project.... In the Add New Project dialog, in the Installed | Templates list, select Visual C#. In the list at the center, select Console App (.NET Core), type the name Ch02_Numbers
, and then click on OK.
In the Solution Explorer window, right-click on the solution and select Properties or press Alt + Enter. For Startup Project, select Current selection. From now on, you can simply click on a project in the Solution Explorer and then press Ctrl + F5 to save, compile, and run that project, as shown in the following screenshot:
Create a new folder inside the Chapter02
folder named Ch02_Numbers
.
In Visual Studio Code, open the Ch02_Numbers
folder and use the Integrated Terminal to create a new console application using the command dotnet new console
. When you open the Program.cs
file, you will be prompted to restore packages.
Type the following code inside the Main
method:
Console.WriteLine($"int uses {sizeof(int)} bytes and can store numbers in the range {int.MinValue:N0} to {int.MaxValue:N0}."); Console.WriteLine($"double uses {sizeof(double)} bytes and can store numbers in the range {double.MinValue:N0} to {double.MaxValue:N0}."); Console.WriteLine($"decimal uses {sizeof(decimal)} bytes and can store numbers in the range {decimal.MinValue:N0} to {decimal.MaxValue:N0}.");
Run the console application by pressing Ctrl + F5, or entering dotnet run
, and view the output:
int uses 4 bytes and can store numbers in the range -2,147,483,648 to 2,147,483,647. double uses 8 bytes and can store numbers in the range - 179,769,313,486,232,000,000,000,000,000,000,000,000,000,000,000,000,0 00,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,00 0,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,0 00,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 to 179,769,313,486,232,000,000,000,000,000,000,000,000,000,000,000,000,0 00,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,00 0,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 ,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,0 00,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000. decimal uses 16 bytes and can store numbers in the range - 79,228,162,514,264,337,593,543,950,335 to 79,228,162,514,264,337,593,543,950,335.
Why might a double
variable be able to store bigger numbers than a decimal
variable yet use half the space in memory? Let's find out!
Under the previous statements, enter the following code. Do not worry about understanding the syntax right now, although it isn't too hard to follow:
double a = 0.1; double b = 0.2; if (a + b == 0.3) { Console.WriteLine($"{a} + {b} equals 0.3"); } else { Console.WriteLine($"{a} + {b} does NOT equal 0.3"); }
Run the console application and view the output:
0.1 + 0.2 does NOT equal 0.3
The double
type is not guaranteed to be accurate. Only use double
when accuracy, especially when comparing two numbers, is not important; for example, when measuring a person's height.
The problem with the preceding code is how the computer stores the number 0.1 or multiples of 0.1. To represent 0.1 in binary, the computer stores 1 in the 1/16 column, 1 in the 1/128 column, 1 in the 1/1024 column, and so on. The number 0.1 in decimal is 0.0001001001001 repeating forever:
4 |
2 |
1 |
. |
½ |
¼ |
1/8 |
1/16 |
1/32 |
1/64 |
1/128 |
1/256 |
1/512 |
1/1024 |
1/2048 |
0 |
0 |
0 |
. |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
Good Practice
Never compare double values using ==
. During the First Gulf War, an American Patriot missile battery used double values in its calculations. The inaccuracy caused it to fail to track and intercept an incoming Iraqi Scud missile, and 28 soldiers were killed; you can read about this at: https://www.ima.umn.edu/~arnold/disasters/patriot.html
Copy and paste the code you wrote before that used double
variables and then modify it to look like the following code:
decimal c = 0.1M; // M indicates a decimal literal value decimal d = 0.2M; if (c + d == 0.3M) { Console.WriteLine($"{c} + {d} equals 0.3"); } else { Console.WriteLine($"{c} + {d} does NOT equal 0.3"); }
Run the console application and view the output:
0.1 + 0.2 equals 0.3
The decimal
type is accurate because it stores the number as a large integer and shifts the decimal point. For example, 0.1 is stored as 1, with a note to shift the decimal point one place to the left. 12.75 is stored as 1275, with a note to shift the decimal point two places to the left.
The double
type has some useful special values; double.NaN
means not-a-number, double.Epsilon
is the smallest positive number that can be stored in a double, and double.Infinity
means an infinitely large value. You can use these special values when comparing double
values.
Booleans can only contain one of the two values: true
or false
, as shown in the following code. They are most commonly used to branch and loop, as you will see in Chapter 3, Controlling the Flow, Converting Types, and Handling Exceptions:
bool happy = true; bool sad = false;
There is a special type named object
that can store any type of data, but its flexibility comes at the cost of messier code and poor performance due to boxing and unboxing operations when storing a value type. You should avoid it whenever possible.
Add a new console application project named Ch02_Variables
and add the following code to the Main
method:
object height = 1.88; // storing a double in an object object name = "Amir"; // storing a string in an object int length1 = name.Length; // gives compile error! int length2 = ((string)name).Length; // cast to access members
The object
type has been available since the first version of C#, but C# 2 and higher versions have a better alternative called generics, which we will cover later, that provide the flexibility we want without the performance overhead.
There is another special type named dynamic
that can also store any type of data, and like object
, its flexibility comes at the cost of performance. Unlike object, the value stored in the variable can have its members invoked without an explicit cast, as shown in the following code:
// storing a string in a dynamic object dynamic anotherName = "Ahmed"; // this compiles but might throw an exception at run-time! int length = anotherName.Length;
The limitation of dynamic
is that Visual Studio cannot show IntelliSense to help you write the code because the compiler doesn't check at build time. Instead, the CLR checks for the member at runtime. The dynamic
keyword was introduced in C# 4.
Local variables are declared inside methods and they only exist during the call to that method. Once the method returns, the memory allocated to any local variables is released.
Enter the following code to declare and assign values to some local variables inside the Main
method. Note that we specify the type before the name of each variable:
int population = 66_000_000; // 66 million in UK double weight = 1.88; // in kilograms decimal price = 4.99M; // in pounds sterling string fruit = "Apples"; // strings use double-quotes char letter = 'Z'; // chars use single-quotes bool happy = true; // Booleans have value of true or false
You can use the var
keyword to declare local variables. The compiler will infer the type from the literal value you assign after the assignment =
operator.
A literal number without a decimal point is inferred as an int
variable unless you add the L
suffix, in which case, it infers a long
variable. A literal number with a decimal point is inferred as double
unless you add the M
suffix, in which case, it infers a decimal
variable, or the F
suffix, in which case, it infers a float
variable. Double quotes indicate a string
variable, single quotes indicate a char
variable, and the true
and false
values infer a bool
.
Modify your code to use var
:
var population = 66_000_000; // 66 million in UK var weight = 1.88; // in kilograms var price = 4.99M; // in pounds sterling var fruit = "Apples"; // strings use double-quotes var letter = 'Z'; // chars use single-quotes var happy = true; // Booleans have value of true or false
Good Practice
Although using var
is convenient, smart developers avoid using it, to make it easier for a code reader to understand the types in use. Personally, I use it only when the type is obvious. For example, in the following code statements, the first statement is just as clear as the second in stating what the type of the xml
variable is, but it is shorter. However, the third statement isn't clear, so the fourth is better. If in doubt, spell it out!
// good use of var var xml1 = new XmlDocument(); // unnecessarily verbose repeating XmlDocument XmlDocument xml2 = new XmlDocument(); // bad use of var; what data type is file1? var file1 = File.CreateText(@"C:something.txt"); // good use of a specific type declaration StreamWriter file2 = File.CreateText(@"C:something.txt");
Most of the primitive types except string
are value types. This means they must have a value. You can determine the default value of a type using the default()
operator. The default value of an int
variable is 0 (zero):
Console.WriteLine($"{default(int)}"); // 0 Console.WriteLine($"{default(bool)}"); // False Console.WriteLine($"{default(DateTime)}"); // 1/01/0001 00:00:00
Strings are reference types. This means that they contain the memory address of a variable, not the value of the variable itself. A reference type variable can have a null
value. The null
value is a special literal value that indicates that the variable does not reference anything (yet).
You will learn more about value types and reference types in Chapter 7, Implementing Interfaces and Inheriting Classes.
Sometimes, it is convenient to allow a value type to be null
. You can do this by adding a question mark as a suffix to the type when declaring a variable, as shown in the following code:
int ICannotBeNull = 4; int? ICouldBeNull = null; Console.WriteLine(ICouldBeNull.GetValueOrDefault()); // 0 ICouldBeNull = 4; Console.WriteLine(ICouldBeNull.GetValueOrDefault()); // 4
It is important to check if a reference type or nullable value type variable currently contains null
because if you do not, a NullReferenceException
can be thrown causing an error in your code.
// check is myVariable is not null before using it if (ICouldBeNull != null) { // do something with ICouldBeNull }
If you are trying to get a field or property from a variable that might be null
, use the null check operator (?.
), as shown in the following code:
string authorName = null; // if authorName is null, instead of throwing an exception, // null is returned int? howManyLetters = authorName?.Length;
Sometimes you want to either assign a variable to a result, or use an alternative value, such as zero, if the variable is null. You do this using the null-coalescing operator (??
), as shown in the following code:
// result will be three if howManyLetters is null var result = howManyLetters ?? 3; Console.WriteLine(result);
When you need to store multiple values of the same type, you can declare an array. For example, you might need to store four names in a string
array.
The following code declares an array for storing four string
values. Then, it stores string
values at index positions 0 to 3 (arrays count from zero, so the last item is one less than the length of the array). Finally, it loops through each item in the array using a for
statement that we will cover in more detail in Chapter 3, Controlling the Flow, Converting Types, and Handling Exceptions.
Add the following lines of code to the end of the Main
method:
// declaring the size of the array string[] names = new string[4]; // storing items at index positions names[0] = "Kate"; names[1] = "Jack"; names[2] = "Rebecca"; names[3] = "Tom"; for (int i = 0; i < names.Length; i++) { Console.WriteLine(names[i]); // read the item at this index }
Arrays are always of a fixed size, so you need to decide how many items you want to store before instantiating them. Arrays are useful for temporarily storing multiple items, but collections are more flexible when adding and removing items dynamically. We will cover collections in Chapter 4, Using .NET Standard Types.
3.128.200.220