Professional C# 6 and .NET Core 1.0. Christian Nagel
Чтение книги онлайн.
Читать онлайн книгу Professional C# 6 and .NET Core 1.0 - Christian Nagel страница 20
Consider the following code snippet (code file VariableScopeSample3/Program.cs):
This code will compile even though you have two variables named j in scope within the Main method: the j that was defined at the class level and doesn’t go out of scope until the class Program is destroyed (when the Main method terminates and the program ends), and the j defined within Main. In this case, the new variable named j that you declare in the Main method hides the class-level variable with the same name, so when you run this code, the number 30 is displayed.
What if you want to refer to the class-level variable? You can actually refer to fields of a class or struct from outside the object, using the syntax object.fieldname. In the previous example, you are accessing a static field (you find out what this means in the next section) from a static method, so you can’t use an instance of the class; you just use the name of the class itself:
If you are accessing an instance field (a field that belongs to a specific instance of the class), you need to use the this keyword instead.
Working with Constants
As the name implies, a constant is a variable whose value cannot be changed throughout its lifetime. Prefixing a variable with the const keyword when it is declared and initialized designates that variable as a constant:
Constants have the following characteristics:
• They must be initialized when they are declared. After a value has been assigned, it can never be overwritten.
• The value of a constant must be computable at compile time. Therefore, you can’t initialize a constant with a value taken from a variable. If you need to do this, you must use a read-only field (this is explained in Chapter 3).
• Constants are always implicitly static. However, notice that you don’t have to (and, in fact, are not permitted to) include the static modifier in the constant declaration.
At least three advantages exist for using constants in your programs:
• Constants make your programs easier to read by replacing magic numbers and strings with readable names whose values are easy to understand.
• Constants make your programs easier to modify. For example, assume that you have a SalesTax constant in one of your C# programs, and that constant is assigned a value of 6 percent. If the sales tax rate changes later, you can modify the behavior of all tax calculations simply by assigning a new value to the constant; you don’t have to hunt through your code for the value .06 and change each one, hoping you will find all of them.
• Constants help prevent mistakes in your programs. If you attempt to assign another value to a constant somewhere in your program other than at the point where the constant is declared, the compiler flags the error.
Now that you have seen how to declare variables and constants, let’s take a closer look at the data types available in C#. As you will see, C# is much stricter about the types available and their definitions than some other languages.
Value Types and Reference Types
Before examining the data types in C#, it is important to understand that C# distinguishes between two categories of data type:
• Value types
• Reference types
The next few sections look in detail at the syntax for value and reference types. Conceptually, the difference is that a value type stores its value directly, whereas a reference type stores a reference to the value.
These types are stored in different places in memory; value types are stored in an area known as the stack, and reference types are stored in an area known as the managed heap. It is important to be aware of whether a type is a value type or a reference type because of the different effect each assignment has. For example, int is a value type, which means that the following statement results in two locations in memory storing the value 20:
However, consider the following example. For this code, assume you have defined a class called Vector and that Vector is a reference type and has an int member variable called Value:
The crucial point to understand is that after executing this code, there is only one Vector object: x and y both point to the memory location that contains this object. Because x and y are variables of a reference type, declaring each variable simply reserves a reference – it doesn’t instantiate an object of the given type. In neither case is an object actually created. To create an object, you have to use the new keyword, as shown. Because x and y refer to the same object, changes made to x will affect y and vice versa. Hence, the code will display 30 and then 50.
If a variable is a reference, it is possible to indicate that it does not refer to any object by setting its value to null:
If a reference is set to null, then clearly it is not possible to call any nonstatic member functions or fields against it; doing so would cause an exception to be thrown at runtime.
In C#, basic data types such as bool and long are value types. This means that if you declare a bool variable and assign it the value of another bool variable, you will have two separate bool values in memory. Later, if you change the value of the original bool variable, the value of the second bool variable does not change. These types are copied by value.
In contrast, most of the more complex C# data types, including classes that you yourself declare, are reference types. They are allocated upon the heap, have lifetimes that can span multiple function calls, and can be accessed through one or several aliases. The CLR implements an elaborate algorithm to track which reference variables are still reachable and which have been orphaned. Periodically, the CLR destroys orphaned objects and returns the memory that they once occupied back to the operating system. This is done by the garbage collector.
C# has been designed this way because high performance is best served by keeping primitive types (such as int and bool) as value types, and larger types that contain many fields (as is usually the case with classes) as reference types. If you want to define your own type as a value type, you should declare it as a struct.
.NET Types
The C# keywords for data types – such as int, short, and string – are mapped from the compiler to .NET data types. For example, when you declare an int in C#, you are actually declaring an instance of a .NET struct: System.Int32. This might sound like a small point, but it has a profound significance: It means that you can treat all the primitive data types syntactically, as if they are classes that support certain methods. For example, to convert an int i to a string, you can write the following:
It should be emphasized that behind this syntactical convenience, the types really are stored as primitive types, so absolutely no performance cost is associated with the idea that the primitive types are notionally represented by .NET structs.
The