C H A P T E R  3

Image

Types, Storage, and Variables

Image A C# Program Is a Set of Type Declarations

Image A Type Is a Template

Image Instantiating a Type

Image Data Members and Function Members

Image Predefined Types

Image User-Defined Types

Image The Stack and the Heap

Image Value Types and Reference Types

Image variables

Image Static Typing and the dynamic Keyword

Image Nullable Types

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 new namespace called MyProgram.

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

Namespaces are covered in more detail in Chapter 10.

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 you 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—a type either provided by the language, provided by 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 14 describes arrays in detail.

Types of Members

Other types, however, 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 types 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++, numeric values do not have a Boolean interpretation in C#.

    The three nonsimple types are the following:

  • Type string, which is an array of Unicode characters
  • Type object, which is the 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 just 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.

Table 3-1. The Predefined Simple Types

Name Meaning Range .NET Framework
Type
Default Value
sbyte 8-bit signed integer -128–127 System.SByte 0
byte 8-bit unsigned integer 0–255 System.Byte 0
short 16-bit signed integer -32,768–32,767 System.Int16 0
ushort 16-bit unsigned integer 0–65,535 System.UInt16 0
int 32-bit signed integer -2,147,483,648–2,147,483,647 System.Int32 0
uint 32-bit unsigned integer 0–4,294,967,295 System.UInt32 0
long 64-bit signed integer -9,223,372,036,854,775,808–9,223,372,036,854,775,807 System.Int64 0
ulong 64-bit unsigned integer 0–18,446,744,073,709,551,615 System.UInt64 0
float Single-precision float 1.5×10-45–3.4×1038 System.Single 0.0f
double Double-precision float 5×10-324–1.7×10308 System.Double 0.0d
bool Boolean true, false System.Boolean false
char Unicode character U+0000–U+ffff System.Char x0000
decimal Decimal value with 28-significant-digit precision ± 1.0×1028–±7.9×1028 System.Decimal 0m

The nonsimple predefined types are somewhat more complex. Values of type string contain zero or more Unicode characters. The object type is the base class for all other types in the system, including the predefined, simple types. Table 3-2 shows the predefined nonsimple types.

Table 3-2. The Predefined Nonsimple Types

Name Meaning .NET Framework Type
object The base class from which all other types are derived System.Object
string A sequence of Unicode characters System.String
dynamic A type designed to be used with assemblies written in dynamic languages

User-Defined Types

Besides the 15 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 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.

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
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 where chunks of memory are allocated to store certain kinds of data objects. Unlike the stack, memory can be stored and removed from the heap in any order. Figure 3-7 shows a program that has stored four items 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 is no longer accessing 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 in the heap the data is stored.

Data that is not a member of another type is stored as shown in Figure 3-9. 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

Figure 3-9 shows how data is stored when it isn't a member of another type. When it's a member of another type, 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.

Table 3-3. Value Types and Reference Types in C#

Value Types Reference Types
Predefined types sbyte
short
int    
long 
bool 
byte
ushort
uint
ulong
float
double
char
decimal
object  
string  
dynamic 
User-defined types  struct
enum 
class    
interface
delegate 
array    

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 categories of variables, each of which will be discussed in detail. These kinds are listed in Table 3-4.

Table 3-4. The Four Kinds of Variables

Name Member of a Type Description
Local variable No Holds temporary data within the scope of a method
Field Yes Holds data associated with a type or an instance of a type
Parameter No A temporary variable used to pass data from one method to another method
Array element Yes One member of a sequenced collection of (usually) homogeneous data items

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:

Image

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 equals sign followed by the initializing value, as shown here:

Image

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 types of variables are automatically initialized and which are not. I'll cover each of the five variable types later in the text.

Table 3-5. Types of Variables

Variable Type Stored In Auto-initialized Use
Local variables Stack or stack and heap No Used for local computation inside a function member
Class fields Heap Yes Members of a class
Struct fields Stack or heap Yes Members of a struct
Parameters Stack No Used for passing values into and out of a method
Array elements Heap Yes Members of an array

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 different types of variables in a single statement.

Image

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 value of var2 is retrieved from memory and placed at the position of the variable.

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

Static Typing and the dynamic Keyword

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

Not all languages, though, are statically typed. Many, including such scripting languages as IronPython and IronRuby, are dynamically typed. That is, the type of a variable might not be resolved until runtime. Since these are .NET languages, C# programs need to be able to use assemblies written in these languages.

To solve the problem that C# needs to be able to resolve at compile time a type referenced in an assembly that doesn't resolve its types until runtime, the C# language designers added the keyword dynamic to the language. The dynamic keyword represents a specific, actual C# type that knows how to resolve itself at runtime. That is, it's statically typed as dynamic!

This satisfies both constraints. The C# compiler can resolve the keyword to an actual type, and the type object can resolve itself to the target assembly's type at runtime.

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.

Creating a Nullable Type

A nullable type is always based on another type, called the underlying type, that has already been declared.

  • You can create a nullable type from any value type, including the predefined, simple types.
  • You cannot create a nullable type from a reference type or from another nullable type.
  • You do not explicitly declare a nullable type in your code. Instead, you declare a variable of a nullable type. The compiler implicitly creates the nullable type for you.

To create a variable of a nullable type, simply add a question mark to the end of the name of the underlying type, in the variable declaration. Unfortunately, this syntax makes it appear that you have a lot of questions about your code.

For example, the following code declares a variable of the nullable int type. Notice that the suffix is attached to the type name—not the variable name.

Image

With this declaration statement, the compiler takes care of both producing the nullable type and creating the variable of that type.

Using a nullable type is almost the same as using a variable of any other type. Reading a variable of a nullable type returns its value. You must, however, make sure that the variable is not null. Attempting to read the value of a null variable produces an exception.

  • Like any variable, to retrieve its value, you just use its name.
  • To check whether a nullable type has a value, you can compare it to null.

Image

Both sets of code produce the following output:


15

You can easily convert between a nullable type and its corresponding non-nullable type. We'll go into conversions in detail in Chapter 18, but the important points for nullable types are the following:

  • There is an implicit conversion between a non-nullable type and its nullable version. That is, no cast is needed.
  • There is an explicit conversion between a nullable type and its non-nullable version.

For example, the following lines show conversion in both directions. In the first line, a literal of type int is implicitly converted to a value of type int? and is used to initialize the variable of the nullable type. In the second line, the variable is explicitly converted to its non-nullable version.

   int? myInt1 = 15;                       // Implicitly convert int to int?
   int regInt = (int) myInt1;             // Explicitly convert int? to int

Assigning to a Nullable Type

You can assign three kinds of values to a variable of a nullable type:

  • A value of the underlying type
  • A value of the same nullable type
  • The value null

The following code shows an example of each of the three types of assignment:

   int? myI1, myI2, myI3;

   myI1 = 28;                                   // Value of underlying type
   myI2 = myI1;                                 // Value of nullable type
   myI3 = null;                                 // Null

   Console.WriteLine("myI1: {0}, myI2: {1}", myI1, myI2);

This code produces the following output:


myI1: 28, myI2: 28

In Chapter 25, when you have a clearer understanding of C#, I'll explain the finer points of nullable types.

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

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