C H A P T E R  3

Types, Storage, and Variables

A C# Program Is a Set of Type Declarations

If you were to broadly characterize the source code of C and C++ programs, you might say that a C program is a set of functions and data types and that a C++ program is a set of functions and classes. A C# program, however, is a set of type declarations.

  • The source code of a C# program or DLL is a set of one or more type declarations.
  • For an executable, one of the types declared must be a class that includes a method called Main.
  • A namespace is a way of grouping a related set of type declarations and giving the group a name. Since your program is a related set of type declarations, you will generally declare your program type inside a namespace you create.

For example, the following code shows a program that consists of three type declarations. The three types are declared inside a namespace called MyProgram.

   namespace MyProgram                         // Declare a namespace.
   {
      DeclarationOfTypeA                       // Declare a type.
   
      DeclarationOfTypeB                       // Declare a type.
   
      class C                                  // Declare a type.
      {
         static void Main()
         {
            ...
         }
      }
   }

Namespaces are explained in detail in Chapter 21.

A Type Is a Template

Since a C# program is just a set of type declarations, learning C# consists of learning how to create and use types. So, the first thing we need to do is to look at what a type is.

You can start by thinking of a type as a template for creating data structures. It isn’t the data structure itself, but it specifies the characteristics of objects constructed from the template.

A type is defined by the following elements:

  • A name
  • A data structure to contain its data members
  • Behaviors and constraints

For example, Figure 3-1 illustrates the components of two types: short and int.

Image

Figure 3-1. A type is a template.

Instantiating a Type

Creating an actual object from the type’s template is called instantiating the type.

  • The object created by instantiating a type is called either an object of the type or an instance of the type. The terms are interchangeable.
  • Every data item in a C# program is an instance of some type provided by the language, the BCL, or another library, or defined by the programmer.

Figure 3-2 illustrates the instantiation of objects of two predefined types.

Image

Figure 3-2. Instantiating a type creates an instance.

Data Members and Function Members

Some types, such as short, int, and long, are called simple types and can store only a single data item.

Other types can store multiple data items. An array, for example, is a type that can store multiple items of the same type. The individual items are called elements and are referenced by a number, called an index. Chapter 12 describes arrays in detail.

Types of Members

There are other types, however, that can contain data items of many different types. The individual elements in these types are called members, and, unlike arrays, in which each member is referred to by a number, these members have distinct names.

There are two kinds of members: data members and function members.

  • Data members store data that is relevant to the object of the class or to the class as a whole.
  • Function members execute code. Function members define how the type can act.

For example, Figure 3-3 shows some of the data members and function members of type XYZ. It contains two data members and two function members.

Image

Figure 3-3. Types specify data members and function members.

Predefined Types

C# provides 16 predefined types, which are shown in Figure 3-4 and listed in Tables 3-1 and 3-2. They include 13 simple types and 3 nonsimple types.

The names of all the predefined types consist of all lowercase characters. The predefined simple types include the following:

  • Eleven numeric types, including the following:
    • Various lengths of signed and unsigned integer types.
    • Floating-point types—float and double.
    • A high-precision decimal type called decimal. Unlike float and double, type decimal can represent decimal fractional numbers exactly. It’s often used for monetary calculations.
  • A Unicode character type, called char.
  • A Boolean type, called bool. Type bool represents Boolean values and must be one of two values—either true or false.

Image Note Unlike C and C++, in C# numeric values do not have a Boolean interpretation.

The three nonsimple types are the following:

  • Type string, which is an array of Unicode characters
  • Type object, which is the base type on which all other types are based
  • Type dynamic, which is used when using assemblies written in dynamic languages
Image

Figure 3-4. The predefined types

More About the Predefined Types

All the predefined types are mapped directly to underlying .NET types. The C# type names are simply aliases for the .NET types, so using the .NET names works fine syntactically, although this is discouraged. Within a C# program, you should use the C# names rather than the .NET names.

The predefined simple types represent a single item of data. They’re listed in Table 3-1, along with the ranges of values they can represent and the underlying .NET types to which they map.

Image

The nonsimple predefined types are somewhat more complex. Table 3-2 shows the predefined nonsimple types.

Image

User-Defined Types

Besides the 16 predefined types provided by C#, you can also create your own user-defined types. There are six kinds of types you can create. They are the following:

  • class types
  • struct types
  • array types
  • enum types
  • delegate types
  • interface types

You create a type using a type declaration, which includes the following information:

  • The kind of type you are creating
  • The name of the new type
  • A declaration (name and specification) of each of the type’s members—except for array and delegate types, which don’t have named members

Once you’ve declared a type, you can create and use objects of the type just as if they were predefined types. Figure 3-5 summarizes the use of predefined and user-defined types. Using predefined types is a one-step process in which you simply instantiate the objects of that type. Using user-defined types is a two-step process. You must first declare the type and then instantiate objects of the type.

Image

Figure 3-5. The predefined types require instantiation only. The user-defined types require two steps: declaration and instantiation.

The Stack and the Heap

While a program is running, its data must be stored in memory. How much memory is required for an item, and where and how it’s stored, depends on its type.

A running program uses two regions of memory to store data: the stack and the heap.

The Stack

The stack is an array of memory that acts as a last-in, first-out (LIFO) data structure. It stores several types of data:

  • The values of certain types of variables
  • The program’s current execution environment
  • Parameters passed to methods

The system takes care of all stack manipulation. You, as the programmer, don’t need to do anything with it explicitly. But understanding its basic functions will give you a better understanding of what your program is doing when it’s running and allow you to better understand the C# documentation and literature.

Facts About Stacks

The general characteristics of stacks are the following:

  • Data can be added to and deleted only from the top of the stack.
  • Placing a data item at the top of the stack is called pushing the item onto the stack.
  • Deleting an item from the top of the stack is called popping the item from the stack.

Figure 3-6 illustrates the functions and terminology of the stack.

Image

Figure 3-6. Pushing and popping on the stack

The Heap

The heap is an area of memory where chunks are allocated to store certain kinds of data objects. Unlike the stack, data can be stored and removed from the heap in any order. Figure 3-7 shows a program that has four items stored in the heap.

Image

Figure 3-7. The memory heap

Although your program can store items in the heap, it cannot explicitly delete them. Instead, the CLR’s garbage collector (GC) automatically cleans up orphaned heap objects when it determines that your code can no longer access them. This frees you from what in other programming languages can be an error-prone task. Figure 3-8 illustrates the garbage collection process.

Image

Figure 3-8. Automatic garbage collection in the heap

Value Types and Reference Types

The type of a data item defines how much memory is required to store it and the data members that comprise it. The type also determines where an object is stored in memory—the stack or the heap.

Types are divided into two categories: value types and reference types. Objects of these types are stored differently in memory.

  • Value types require only a single segment of memory, which stores the actual data.
  • Reference types require two segments of memory:
    • The first contains the actual data—and is always located in the heap.
    • The second is a reference that points to where the data is stored in the heap.

Figure 3-9 shows how a single data item of each type is stored. For value types, data is stored on the stack. For reference types, the actual data is stored in the heap, and the reference is stored on the stack.

Image

Figure 3-9. Storing data that is not part of another type

Storing Members of a Reference Type Object

Although Figure 3-9 shows how data is stored when it isn’t a member of another object, when it’s a member of another object, data might be stored a little differently.

  • The data portion of a reference type object is always stored in the heap, as shown in Figure 3-9.
  • A value type object, or the reference part of a reference type, can be stored in either the stack or the heap, depending on the circumstances.

Suppose, for example, that you have an instance of a reference type called MyType that has two members—a value type member and a reference type member. How is it stored? Is the value type member stored on the stack and the reference type split between the stack and the heap, as shown in Figure 3-9? The answer is no.

Remember that for a reference type, the data of an instance is always stored in the heap. Since both members are part of the object’s data, they’re both stored in the heap, regardless of whether they are value or reference types. Figure 3-10 illustrates the case of type MyType.

  • Even though member A is a value type, it’s part of the data of the instance of MyType and is therefore stored with the object’s data in the heap.
  • Member B is a reference type, and therefore its data portion will always be stored in the heap, as shown by the small box marked “Data.” What’s different is that its reference is also stored in the heap, inside the data portion of the enclosing MyType object.
Image

Figure 3-10. Storage of data as part of a reference type

Image Note  For any object of a reference type, all its data members are stored in the heap, regardless of whether they are of value type or reference type.

Categorizing the C# Types

Table 3-3 shows all the types available in C# and what kinds of types they are—value types or reference types. Each reference type is covered later in the text.

Image

Variables

A general-purpose programming language must allow a program to store and retrieve data.

  • A variable is a name that represents data stored in memory during program execution.
  • C# provides four kinds of variables, each of which will be discussed in detail. These are listed in Table 3-4.

Image

Variable Declarations

A variable must be declared before it can be used. The variable declaration defines the variable and accomplishes two things:

  • It gives the variable a name and associates a type with it.
  • It allows the compiler to allocate memory for it.

A simple variable declaration requires at least a type and a name. The following declaration defines a variable named var2, of type int:

   Type
    
   int var2;
        
      Name

For example, Figure 3-11 represents the declaration of four variables and their places on the stack.

Image

Figure 3-11. Value type and reference type variable declarations

Variable Initializers

Besides declaring a variable’s name and type, you can optionally use the declaration to initialize its memory to a specific value.

A variable initializer consists of an equal sign followed by the initializing value, as shown here:

           Initializer
               ↓   
  int var2 = 17;

Local variables without initializers have an undefined value and cannot be used until they have been assigned a value. Attempting to use an undefined local variable causes the compiler to produce an error message.

Figure 3-12 shows a number of local variable declarations on the left and the resulting stack configuration on the right. Some of the variables have initializers, and others do not.

Image

Figure 3-12. Variable initializers

Automatic Initialization

Some kinds of variables are automatically set to default values if they are declared without an initializer, and others are not. Variables that are not automatically initialized to default values contain undefined values until the program assigns them a value. Table 3-5 shows which kinds of variables are automatically initialized and which are not. I’ll cover each of the five kinds of variables later in the text.

Image

Multiple-Variable Declarations

You can declare multiple variables in a single declaration statement.

  • The variables in a multiple-variable declaration must all be of the same type.
  • The variable names must be separated with commas. Initializers can be included with the variable names.

For example, the following code shows two valid declaration statements with multiple variables. Notice that the initialized variables can be mixed with uninitialized variables as long as they’re separated by commas. The last declaration statement shown is invalid because it attempts to declare variables of different types in a single statement.

   // Variable declarations--some with initializers, some without
   int    var3 = 7, var4, var5 = 3;
   double var6, var7 = 6.52;
   
   Type     Different type
              
   int var8, float var9;       // Error! Can't mix types (int and float)

Using the Value of a Variable

A variable name represents the value stored by the variable. You can use the value by using the variable name.

For example, in the following statement, the variable name var2 represents the value stored by the variable. That value is retrieved from memory when the statement is executed.

   Console.WriteLine("{0}", var2);

Static Typing and the dynamic Keyword

One thing you’ll have noticed is that every variable declaration includes the type of the variable. This allows the compiler to determine the amount of memory it will require at run time and which parts should be stored on the stack and which in the heap. The type of the variable is determined at compile time and cannot be changed at run time. This is called static typing.

Not all languages, though, are statically typed. Many, including scripting languages such as IronPython and IronRuby, are dynamically typed. That is, the type of a variable might not be resolved until run time. Since these are also .NET languages, C# programs need to be able to use assemblies written in these languages. The problem, then, is that C# needs to be able to resolve at compile time a type from an assembly that doesn’t resolve its types until run time.

To solve this problem, C# provides the dynamic keyword to represent a specific C# type that knows how to resolve itself at run time.

At compile time, the compiler doesn’t do type checking on variables of type dynamic. Instead, it packages up any information about the variable’s operations and includes that information with the variable. At run time, that information is checked to make sure it’s consistent with the actual type to which the variable was resolved. If it doesn’t, the run time throws an exception.

Nullable Types

There are situations, particularly when working with databases, where you want to indicate that a variable does not currently hold a valid value. For reference types, you can do this easily, by setting the variable to null. When you define a variable of a value type, however, its memory is allocated whether or not its contents have any valid meaning.

What you would like in this situation is to have a Boolean indicator associated with the variable so that, when the value is valid, the indicator is true, and when the value is not valid, the indicator is false.

Nullable types allow you to create a value type variable that can be marked as valid or invalid so that you can make sure a variable is valid before using it. Regular value types are called non-nullable types. I’ll explain the details of nullable types in Chapter 25, when you have a better understanding of C#.

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

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