Chapter 4
Data Types, Variables, and Constants

What’s in This Chapter

  • Value versus reference types
  • Variable initialization
  • Widening and narrowing conversions
  • Passing parameters by value, by reference, and for output
  • Nullable types
  • Enumerations and delegates

Wrox.com Downloads for This Chapter

Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/csharp5programmersref on the Download Code tab.

A variable is a program element that stores a value. Some of the values that a variable might contain include a number, string, character, date, or object representing something complex such as a customer or business report.

A program uses variables to hold and manipulate values. For example, if some variables hold numbers, the program can apply arithmetic operations to them. If the variables hold strings, the program can use string operations on them such as concatenating them, searching them for particular substrings, and extracting substrings from them.

Four factors determine a variable’s exact behavior:

  • Data type determines the kind of the data the variable can hold (integer, character, string, and so forth).
  • Scope defines the code that can access the variable. For example, if you declare a variable inside a for loop, only other code inside the loop can use the variable. If you declare a variable at the top of a method, only the code in the method can use the variable.
  • Accessibility determines what code in other modules can access the variable. If you declare a variable inside a class at the class level (outside of any method in the class) and you use the private keyword, only the code in the class (or derived classes) can use the variable. In contrast, if you declare the variable with the public keyword, code in other classes can use the variable, too.
  • Lifetime determines how long the variable’s value is valid. For example, a variable declared inside a method is created when the method begins and is destroyed when it exits. If the method runs again, it creates a new copy of the variable and its value is reset.

Visibility is a concept that combines scope, accessibility, and lifetime. It determines whether a certain piece of code can use a variable. If the variable is accessible to the code, the code is within the variable’s scope, and the variable is within its lifetime (has been created and not yet destroyed), then the variable is visible to the code.

This chapter explains the syntax for declaring variables in C#. It explains how you can use different declarations to determine a variable’s data type, scope, accessibility, and lifetime. It discusses some of the issues you should consider when selecting a type of declaration and describes some concepts, such as anonymous and nullable types, that can complicate variable declarations. This chapter also explains ways you can initialize objects, arrays, and collections quickly and easily.

Constants, parameters, and properties all have concepts of scope and data type that are similar to those of variables, so they are also described here.

Data Types

The smallest piece of data a computer can handle is a bit, a single value that can be either 0 or 1. (Bit is a contraction of “binary digit.”)

Eight bits are grouped into a byte. Computers typically measure disk space and memory space in kilobytes (1,024 bytes), megabytes (1,024 kilobytes), gigabytes (1,024 megabytes), and terabytes (1,024 gigabytes).

Multiple bytes are grouped into words that may contain 2, 4, or more bytes depending on the computer hardware. Most computers these days use 4-byte (32-bit) words, although 8-byte (64-bit) computers are becoming more common.

C# also groups bytes in different ways to form data types with a greater logical meaning. For example, it uses 4 bytes to make an integer, a numeric data type that can hold values between −2,147,483,648 to 2,147,483,647.

The following table summarizes C#’s elementary data types.

NameTypeSizeValues
Booleanbool2 bytes True or False.
Bytebyte1 byte0 to 255.
Signed bytesbyte1 byte−128 to 127.
Characterchar2 bytes0 to 65,535.
Short integershort2 bytes−32,768 to 32,767.
Unsigned short integerushort2 bytes0 through 65,535.
Integerint4 bytes−2,147,483,648 to 2,147,483,647.
Unsigned integeruint4 bytes0 through 4,294,967,295.
Long integerlong8 bytes−9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
Unsigned long integerulong8 bytes0 through 18,446,744,073,709,551,615.
Decimaldecimal16 bytes0 to +/−79,228,162,514,264,337,593,543,950,335 with no decimal point; 0 to +/−7.9228162514264337593543950335 with 28 places to the right of the decimal place.
Single-precision floating point numberfloat4 bytes−3.4028235E+38 to −1.401298E-45 (negative values); 1.401298E-45 to 3.4028235E+38 (positive values).
Double-precision floating point numberdouble8 bytes−1.79769313486231570E+308 to −4.94065645841246544E-324 (negative values); 4.94065645841246544E-324 to 1.79769313486231570E+308 (positive values).
StringstringvariesDepending on the platform, a string can hold approximately 0 to 2 billion Unicode characters.
Date and timeDateTime8 bytesJanuary 1, 0001 0:0:00 to December 31, 9999 11:59:59 pm.
Objectobject4 bytesPoints to any type of data.
ClassclassvariesClass members have their own ranges.
StructurestructvariesStructure members have their own ranges.

Many of these data types are actually C#-style shorthand for types defined in the System namespace. For example, sbyte is the same as System.SByte and ulong is the same as System.UInt64.

Normally in a program you can think of the char data type as holding a single character. That could be a simple Roman letter or digit, but C# uses 2-byte Unicode characters, so the char type can also hold more complex characters from other alphabets such as Greek, Kanji, and Cyrillic.

The int data type usually provides the best performance of the integer types, so you should stick with int unless you need the extra range provided by long and decimal, or you need to save space with the smaller char and byte types. In many cases, the space savings you get using the char and byte data types isn’t worth the extra time and effort, unless you work with a large array of values.

Note that you cannot safely assume that a variable’s storage requirements are exactly the same as its size. In some cases, the program may move a variable so that it begins on a boundary that is natural for the hardware platform. For example, if you make a structure containing several short (2-byte) variables, the program may insert 2 extra bytes between them so that they can all start on 4-byte boundaries because that may be more efficient for the hardware. For more information on structures, see Chapter 12, “Classes and Structures.”

Some data types also come with some additional overhead. For example, an array stores some extra information about each of its dimensions.

Value Versus Reference Types

There are two kinds of variables in C#: value types and reference types.

A value type is a relatively simple data type such as an int or float that represents the data it contains directly. If you declare an int variable named numItems and assign it the value 27, the program allocates a chunk of memory and stores the value 27 in it.

In contrast, a reference type variable contains a reference to another piece of memory that actually contains the variable’s data. For example, suppose you define an OrderItem class that has PartNumber, PriceEach, and Quantity properties. Now suppose your program creates an OrderItem object named item1 that has PartNumber = 3618, PriceEach = 19.95, and Quantity = 3. The program allocates a chunk of memory to hold those property values. It also creates another piece of memory that is a reference to the first piece of memory. The variable named item1 is actually this reference and not the memory containing the properties.

Figure 4-1 shows how the two variables numItems and item1 are stored in memory. The dark box on the right shows the pieces of memory that are part of the object referred to by item1.

c04f001.eps

Figure 4-1: Value type variables hold their values directly. Reference type variables hold references to their data.

Most of the types described in the previous section that hold a single piece of data are value types. Those include the numeric types, bool, and char.

Class and structure data types hold multiple related values so, looking at Figure 4-1, you might assume they are reference types. Actually, classes are reference types but structures are value types. That’s one of the biggest differences between the two. Chapter 12 has lots more to say about classes, structures, and their differences.

The DateTime data type is a structure that holds information about a date and time. Like other structures, it is a value type.

Perhaps the most unexpected fact about value and reference types is that the string class is a reference type. A string variable contains a reference to some information that describes the actual textual value.

The var Keyword

The var keyword is like a special data type that makes Visual Studio determine the data type that a variable should have based on the value that it is assigned. For example, the following code uses the var keyword to declare the variable numTypes.

var numTypes = 13;

This code assigns the value 13 to the variable numTypes. Because C# interprets the literal value 13 as an int, the program makes numTypes an int.

You can only use the var keyword inside methods, and you must assign a value to the variable when you declare it (so Visual Studio can figure out what type it should be).

The var keyword is powerful because it can handle all sorts of data types. For example, the following code uses var to declare an array of int and an object with an anonymous type.

var values = new[] { 1, 2, 3 };
var person = new { FirstName = "Rod", LastName = "Stephens" };

Some programmers use the var type extensively. Unfortunately, to correctly understand the code, you need to easily determine the data type that Visual Studio assigns to the variable. For example, see if you can quickly determine the data types assigned to each of the following variables.

var value1 = 100;
var value2 = 1000000000;
var value3 = 10000000000;
var value4 = 100000000000000000000;
var value5 = 1.23;
var value6 = new { Description= "Pencils", Quantity = 12, PriceEach = 0.25m };

The first five data types are int, int, long (because int is too small), syntax error (because this value is too big to fit in a long but Visual Studio won’t automatically promote it to a float), and double.

It’s not too hard to figure out that last value is an object with the three fields Description, Quantity, and PriceEach. What’s less obvious is that this object has a class type and not a structure type. Even worse, suppose the program later uses the following code.

var value7 = new { Description= "Notepad", Quantity = "6", PriceEach = 1.15m };

This code is similar to the previous code, but here the Quantity value is a string, not an int. If you don’t notice that the two declarations have slightly different formats, you won’t know that the two variables have different data types.

To avoid possible confusion, I generally use explicit data types except where var is necessary. In particular, the data types created by LINQ expressions can be weird and hard to discover, so for LINQ using var makes sense. Chapter 8 says more about LINQ.

Variable Declaration Syntax

Inside a method, the syntax for declaring a variable is simple.

«const» type«[]» name «= value»;

The pieces of this declaration are

  • const—If you include this, the variable is a constant and its value cannot be changed later. Use the value to assign the constant a value.
  • type—The data type you want the variable to have.
  • []—Include empty square brackets [] to make an array.
  • name—The name you want the variable to have.
  • = value—The value you want the variable to initially have.

For example, the following snippet declares two variables, an int initialized to 13 and an array of bool.

int numPlayers = 13;
bool[] isActive;

To create multidimensional arrays, include commas to indicate the number of dimensions. For example, the following code declares a two-dimensional array.

int[,] values;

You would access a value in this array as in the following code.

values[1, 2] = 1001;

You can include as many commas as you like to create higher-dimensional arrays.

Declaring a variable that is not inside a method is slightly more complicated because the declaration can include attributes, access specifiers, and other modifiers. The following text shows the syntax for declaring a variable inside a class but not inside any method.

«attributes» «accessibility»
«const | readonly | static | volatile | static volatile»
type«[]» name «= value»

The pieces of this declaration are

  • attributes—One or more attributes that specify extra properties for the variable. The following section, “Attributes,” describes attributes in more detail.
  • accessibility—This determines which code can access the variable. The section “Accessibility” later in this chapter describes accessibility values in more detail.
  • const—If you include this, the variable is a constant and its value cannot be changed later. Use the value to assign the constant a value.
  • readonly—If you include this, the variable is similar to a constant except its value can be set either with a value clause or in the class’s constructor.
  • static—This keyword indicates the variable is shared by all instances of the class.
  • volatile—This keyword indicates the variable might be modified by code running in multiple threads running at the same time.
  • type—The data type you want the variable to have.
  • []—Include empty square brackets [] to make an array.
  • name—The name you want the variable to have.
  • = value—The value you want the variable to initially have.

For example, the following code defines a publically visible constant int variable named NumSquares and initializes it to the value 8.

public const int NumSquares = 8;

The section “Static, Constant, and Volatile Variables” later in this chapter provides more detail on the static, const, readonly, and volatile keywords.

You can define and even initialize multiple variables of the same type in a single statement. The following statement declares and initializes two int variables.

public int value1 = 10, value2 = 20;

Name

A variable’s name must be a valid C# identifier. It should begin with a letter, underscore, or @ symbol. After that it can include letters, numbers, or underscores. If the name begins with @, it must include at least one other character.

Identifier names cannot contain special characters such as &, %, #, and $. They also cannot be the same as C# keywords such as if, for, and public. The following table lists some examples.

NameValid?
numEmployeesValid
NumEmployeesValid
num_employeesValid
_managerValid (but unusual)
_Valid (but confusing)
1st_employeeInvalid (doesn’t begin with a letter, underscore, or @ symbol)
#employeesInvalid (contains the special character #)
returnInvalid (keyword)

The @ character is mainly used to allow a program to have a variable with the same name as a keyword. For example, you could define a variable named @for. The @ symbol tells the compiler that this is not a keyword. However, the compiler ignores the @ symbol after it decides it isn’t the beginning of a keyword. For example, if you declare a variable named @test, then the program considers test and @test to be the same name.

You can avoid a lot of potential confusion if variable names aren’t keywords, don’t use the @ symbol, and aren’t weird combinations such as _, ____, and _1_2_. For a list of C# keywords, go to http://msdn.microsoft.com/library/x53a06bb.aspx.

Attributes

The optional attribute list is a series of attribute objects that provide extra information about the variable. An attribute further refines the definition of a variable to give more information to the compiler, the runtime system, and other tools that need to manipulate the variable.

Attributes are fairly specialized and address issues that arise when you perform specific programming tasks. For example, serialization is the process of converting objects into a textual representation. When you write code to serialize and deserialize data, you can use serialization attributes to gain more control over the process.

The following code defines the OrderItem class. This class declares three public variables: ItemName, Price, and Quantity. It uses attributes to indicate that ItemName should be stored as text, Price should be stored as an XML attribute named Cost, and Quantity should be stored as an XML attribute with its default name, Quantity.

[Serializable()]
public class OrderItem
{
    [XmlText()]
    public string ItemName;

    [XmlAttribute(AttributeName = "Cost")]
    public decimal Price;

    [XmlAttribute()]
    public int Quantity;
}

(These attributes are defined in the System.Xml.Serialization namespace, so the program uses the statement using System.Xml.Serialization, although that statement isn’t shown in the code here.)

The following code shows the XML serialization of an OrderItem object.

<OrderItem Cost="1.25" Quantity="12">Cookie</OrderItem>

Chapter 25, “Serialization,” says more about serialization. Because attributes are so specialized, they are not described in more detail here. For more information, see the sections in the online help related to the tasks you need to perform. For information on attributes in general, see these web pages:

Accessibility

A variable declaration’s accessibility clause can take one of the following values (in order of decreasing accessibility):

  • public—Indicates the variable should be available to all code inside or outside of the variable’s class. This allows the most access to the variable.
  • internal—Indicates the variable should be available to all code inside or outside of the variable’s class within the same assembly only. The difference between this and public is that public allows code in other assemblies to access the variable. The internal keyword is useful, for example, if you write a library for use by other assemblies and you want some of the variables inside the library to be visible only inside the library.
  • protected—Indicates the variable should be accessible only to code within the same class or a derived class. The variable is available to code in the same class or a derived class, even if the instance of the class is different from the one containing the variable. For example, one Employee object can access a protected variable inside another Employee object.
  • internal protected—This is the union of the internal and protected keywords. It indicates a variable is accessible only to code within the same class or a derived class and only within the same assembly.
  • private—Indicates the variable should be accessible only to code in the same class or structure. The variable is available to other instances of the class or structure. For example, the code in one Customer object can access a private variable inside another Customer object.

If you omit the accessibility, a declaration is private by default. In the following code, the two variables value1 and value2 are both private.

private int value1;
int value2;

Static, Constant, and Volatile Variables

A variable declaration can include any of the following keywords:

  • const
  • readonly
  • static
  • volatile
  • static volatile

The const keyword indicates the value cannot be changed after it is created. The variable’s declaration must include an initialization statement to give the constant a value. If you don’t include an initialization or if the code tries to change the constant’s value, Visual Studio flags the statement as an error.

The readonly keyword makes the variable similar to a constant except its value can be set in its declaration or in a class constructor. The following code shows how you could create a Car class with a readonly MilesPerGallon variable.

class Car
{
    public readonly float MilesPerGallon = 40f;

    public Car()
    {
        MilesPerGallon = 20;
    }
    public Car(float milesPerGallon)
    {
        MilesPerGallon = milesPerGallon;
    }
}

The class starts by declaring MilesPerGallon, initially setting it to the somewhat optimistic value 40.

Next, a parameterless constructor sets MilesPerGallon to 20. When the program uses this constructor to create a new Car instance, its MilesPerGallon value is set to 20.

A second constructor takes a float value as a parameter and sets the new instance’s MilesPerGallon value to the parameter’s value. Because all the class’s constructors set MilesPerGallon, the declaration of the variable doesn’t need to give it a value, too. (Chapter 12 covers classes and constructors in greater detail.)

No other code either inside the class or outside of it can modify the readonly variable’s value.

The static keyword indicates the variable is shared by all instances of the class. If a variable is not declared static, each instance of the class has its own copy of the variable.

For example, suppose you build a Car class to represent a fleet of identical cars. Each Car object needs its own Miles property because each car may have driven a different number of miles. However, if all the cars get the same number of miles per gallon, they can share a MilesPerGallon property. The following code shows how you might create this class.

class Car
{
    public static float MilesPerGallon;
    public float Miles;
}

Because all the instances of the Car class share the same MilesPerGallon variable, if the code in any instance of the class changes this value, all the instances see the new value.

The volatile keyword indicates the variable might be modified by code running in multiple threads running at the same time. This prevents the compiler from optimizing the variable in a way that would prevent code on a separate thread from modifying the value. For more information on this keyword, see http://msdn.microsoft.com/library/x13ttww7.aspx.

Initialization

The final (and optional) part of a variable declaration is initializing it.

If you do not initialize a variable, it takes a default value that depends on its data type. Numeric and char variables take the value 0, and bool variables take the value false.

Structures are also value types. When a structure is declared, each of its properties and fields takes its default value. For example, if a structure has a int field, it is set to 0.

Reference values (including class variables and strings) get the special value null, which means “this reference doesn’t point to anything.”

If you don’t want a variable to take its default value, you can include an initialization. Follow the variable’s name with an equal sign and the value you want it to take. For simple types such as int and bool, this is straightforward. For example, the following code declares the bool variable ready and initializes it to the value false.

bool ready = false;

For more complex data types such as classes, structures, arrays, and lists, initialization is a bit more complicated. The following sections explain how to initialize variables of those types.

Classes and Structures

There are two main ways to initialize an object that has a class or structure type. (The steps are the same for classes and structures, so the following text assumes you are working with a class.)

First, you can use a new statement to create the new object and follow it with a list of property or field initializers. Each initializer consists of the property’s or field’s name, an equal sign, and the value that it should receive.

For example, suppose you define the following Person class.

class Person
{ 
    public string FirstName, LastName;
}

Now the program can use the following code to create and initialize an instance of the class.

Person rod = new Person() { FirstName = "Rod", LastName = "Stephens" };

The properties (or fields) do not need to be listed in the order in which they are defined in the class.

The second way to initialize an instance of a class is to give the class a constructor that takes parameters it can use to initialize the object. For example, consider the following Person class.

class Person
{
    public string FirstName, LastName;

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

This code uses the firstName and lastName parameters to initialize the object’s FirstName and LastName fields.

Now the program can use the following code to create and initialize an instance of the class.

Person rod = new Person("Rod", "Stephens"); 

With this approach, the new statement must provide the parameters in the order expected by the constructor.

Arrays

When you declare an array, a C# program doesn’t automatically create the array. After declaring the array, there are two ways you can initialize it.

First, you can follow the variable name with the equal sign, the new keyword, the array items’ data type, and the number of items you want the array to hold surrounded by square brackets. The following code uses this method to create an array of 10 decimal values.

decimal[] salaries = new decimal[10];

All array indices start with 0, so this creates an array with values salaries[0] through salaries[9].

Initially array entries take on their default values. For example, the salaries array initialized in the preceding code would be filled with 10 copies of the value 0. The code can then loop through the array and initialize each entry.

Instead of initializing each array entry separately, you can use the second method for initializing an array. For this method, follow the variable’s name with the equal sign and a comma-delimited list of values surrounded by braces. For example, the following code declares a decimal array and fills it with four values.

decimal[] salaries =
{
    32000m,
    51700m,
    17900m,
    87300m,
};

When you use this method for initializing an array, the program determines the number of items in the array by looking at the values you supply.

If you are initializing an array of objects, the items inside the braces would be values of the appropriate type. For example, the following code declares and initializes an array containing four Person references, the last of which is initialized to null.

Person[] customers =
{
    new Person() { FirstName="Ann", LastName="Archer"},
    new Person() { FirstName="Ben", LastName="Blather"},
    new Person() { FirstName="Cindy", LastName="Carver"},
    null,
};

To initialize a multidimensional array, include an array initializer for each entry. For example, the following code declares and initializes a two-dimensional array.

int[,] values =
{
    {1, 2, 3},
    {4, 5, 6},
};

Note that you must provide a consistent number of items for each of the array’s dimensions. For example, the following declaration is invalid because the array’s first row contains three elements, but the second row contains only two elements.

int[,] values =
{
    {1, 2, 3},
    {4, 5},
};

To initialize an array of arrays, make an array initializer where each item is a new array. The following code declares and initializes an array of arrays holding values similar to those in the preceding two-dimensional array.

int[][] values2 =
{
    new int[] {1, 2, 3},
    new int[] {4, 5, 6},
};

Collections

Collection classes that provide an Add method (such as List, Dictionary, and SortedDictionary) have their own initialization syntax that is similar to a combination of the two kinds of array initializers. After the variable’s name, include the equal sign and a new object as you would for any other class. Follow that with a comma-delimited list of values that should be added to the collection surrounded by braces.

For example, the following code declares and initializes a List<string> (a list of strings).

List<string> pies = new List<string>
{
    "Apple", "Banana", "Cherry", "Coconut Cream"
};

The items inside the braces must include all the values needed by the collection’s Add method. For example, the Dictionary class’s Add method takes two parameters giving a key/value pair that should be added. That means each entry in the initializer should include a key and value.

The following code initializes a Dictionary<string, string> (dictionary with keys that are strings and associated values that are strings). The parameters to the class’s Add method are an item’s key and value so, for example, the value 940-283-1298 has the key Alice Artz. Later you could look up Alice’s phone number by searching the Dictionary for the item with the key "Alice Artz".

Dictionary<string, string> directory = new Dictionary<string, string>()
{
    {"Alice Artz", "940-283-1298"},
    {"Bill Bland", "940-237-3827"},
    {"Carla Careful", "940-237-1983"}
};

Literal Type Characters

If your code includes a literal value such as a number, C# uses a set of rules to interpret the value. For example, the value 2000000000 fits in the int data type, so when a C# program sees that value, it assumes it is an int.

In contrast, the value 3000000000 does not fit in the int data type, so the program assumes this value is a uint, which is big enough to hold the value. A uint cannot hold a negative value, however, so if the program contains the value –3000000000, C# makes it a long.

When a value looks like an integer, the program tries to interpret it as the smallest integer data type at least as large as int (so it doesn’t consider byte, sbyte, short, or ushort).

When a value includes a decimal point, the program assumes it is a double.

For the smaller integer data types, the program automatically converts integer values if possible. For example, consider the following statement.

short count = 15000;

This statement declares a variable named count that has type short. The program considers the literal value 15000 to be an int. Because the value 15000 can fit in a short, the program converts the int into a short and stores the result in the variable.

Often this all works without any extra work on your part, but occasionally it can cause problems. The following code demonstrates one of the most common of those.

float distance = 1.23;

This statement declares a variable named distance that has type float. The program considers the literal value 1.23 to be a double. Because all double values cannot necessarily fit in a float, the program flags this as an error at design time and displays this error:

Literal of type double cannot be implicitly converted to type ‘float’; use an ‘F’ suffix to create a literal of this type

One way to avoid this problem is to use a literal type character to tell C# what type the literal should have. The following code solves the preceding code’s problem. The f at the end of the literal tells the program that the value 1.23 should be treated as a float instead of a double.

float distance = 1.23f;

The following table lists C#’s literal type characters.

CharacterData Type
Uuint
Llong
UL, LUulong
Ffloat
Ddouble
Mdecimal

You can use uppercase or lowercase for literal type characters. For example, 1.23f and 1.23F both give a float result. For the UL and LU characters, you can even mix case as in 10uL; although, that might make the code more confusing than necessary.

C# also lets you precede an integer literal with 0x or 0X (in both cases, the first character is a zero not the letter O) to indicate that it is a hexadecimal (base 16) value. For example, the following two statements set the variable flags to the same value. The first statement uses the decimal value 100 and the second uses the hexadecimal value 0x64.

flags = 100;   // Decimal 100.
flags = 0x64;  // Hexadecimal 0x64 = 6 * 16 + 4 = 100.

Surround string literals with double quotes and char literals with single quotes, as shown in the following code.

string name = "Rod";
char ch = 'a';

Within a string or char literal, a character that follows the character has a special meaning. For example, the combination represents a new line. These combinations are called escape sequences. The following table lists C#’s escape sequences.

Escape SequenceCharacter
Single quote
Double quote
\Backslash
Null
aAlert (bell)
Backspace
fForm feed
New line
Carriage return
Tab
vVertical tab

For example, the following code defines a string that contains four column headers’ values separated by tabs.

string header = "Item	Price Each	Quantity	Total";

If a string contains backslashes but no escape sequences, it can be cumbersome to represent each backslash with \. For example, Windows file paths such as C:TempProjectsCurrentWorkproject.txt may include a lot of backslashes. The following code shows how you would assign this value to a string variable.

string filename = "C:\Temp\Projects\CurrentWork\project.txt";

In C# you can place an @ symbol in front of a string to make working with this kind of text easier. In that case the string ignores all escape sequences and includes the characters between its quotes as they appear.

string filename = @"C:TempProjectsCurrentWorkproject.txt";

This kind of string continues until it reaches its closing " and can even span multiple lines.

If you need the string to contain a " character, you can’t simply type it in the text because the program would think it represented the end of the string. You also cannot use " because this kind of string ignores escape sequences.

Instead of adding a " character to the string, double up the character. The following code initializes a string variable to hold a two-line string that contains double quotes and then displays it in the Console window.

            string greeting = @"Welcome to Project
""Do What I Meant""";
            Console.WriteLine(greeting);

The following text shows the result.

Welcome to Project
"Do What I Meant"

Data Type Conversion

Normally, you assign a value to a variable that has the same data type as the value. For example, you assign a string value to a string variable, you assign an integer value to an int variable, and so forth. Often, however, a program must convert a value from one data type to another. For example, it might need to convert an int into a float or it might need to convert a string containing a numeric value into a decimal.

The following sections discuss several ways you can convert data from one to another. Those methods include

  • Implicit conversion
  • Casting
  • Using the as operator
  • Parsing
  • Using System.Convert
  • Using System.BitConverter

Implicit Conversion

Suppose a program needs to save a value in a variable. If the variable can hold any value that the program might want to use, then the program can safely store the value in the variable.

For example, suppose the program creates an int variable and puts some value in it. It then wants to save the value in a long variable. A long variable can hold any value that an int variable can hold, so this is guaranteed to succeed.

Storing a value in a variable of a different type that is guaranteed to hold the value is called a widening conversion. Storing an int value in a long is an example of a widening conversion. (If you think of the variables as envelopes or boxes, this makes sense. You can put a smaller value in a wider envelope without trouble.)

In contrast, a narrowing conversion is one in which the source value cannot necessarily fit in the destination variable. For example, suppose a program has an int variable and wants to copy its value into a byte variable. This may or may not succeed depending on the value. For example, if the value is 10, this succeeds because 10 will fit in a byte variable. However, if the value is 900, this fails because 900 cannot fit in a byte.

A C# program can make widening conversions implicitly. For example, the following code saves an int value in a long variable.

int theInt = 1337;
long theLong = theInt;

A C# program will not perform implicit narrowing conversions. For example, the following code is not allowed even though the value stored in theLong would fit in an int variable.

long theLong = 100;
int theInt = theLong;

In this case, Visual Studio displays the following error message:

Cannot implicitly convert type ‘long’ to ‘int’. An explicit conversion exists (are you missing a cast?)

If you need to perform a narrowing conversion, then you must use one of the explicit conversion methods described in the following sections. The most common of those methods, and the one suggested by the error message, is casting.

For a table listing C#’s implicit numeric conversions, see http://msdn.microsoft.com/library/y5b434w4.aspx.

For a table listing C#’s explicit numeric conversions, see http://msdn.microsoft.com/library/aa288039.aspx.

Casting

A cast operator explicitly tells a program to convert a value from one type to another type. To cast a value into a type, place the type surrounded by parentheses in front of the value. For example, the following code casts a long value into an int variable. The bold text shows the cast operator.

long theLong = 100;
int theInt = (int)theLong;

The following sections provide more detail about casting numbers and casting objects.

Casting Numbers

If the long value fits in the int variable, then this cast succeeds. If the value does not fit, then the cast fails. By default, C# does not warn you that the cast failed. Instead the converted value causes an overflow or underflow and leaves garbage in the int variable.

You can protect the code by enclosing it in a checked block. If an integer cast overflows or underflows while inside a checked block, the program throws a System.OverflowException. (A program throws different kinds of exception objects to signal runtime errors. Chapter 9, “Error Handling,” has more to say about exceptions and how to handle them.) The following code shows the preceding example rewritten to use a checked block.

checked
{
    theLong = 3000000000;
    theInt = (int)theLong;
}

Now the program can catch the exception as described in Chapter 9.

Unfortunately a checked block does not throw an exception if the program tries to convert a double value into a float and the value won’t fit. For example, the following code tries to squeeze the value –1e200 into a variable of type float.

float theFloat;
double theDouble = -1e200;

theFloat = (float)theDouble;

The value –1e200 is too small to fit in a float so this cast fails. The program doesn’t throw an exception, and a checked block will not detect this error.

If the value is too large or small, the program sets the variable’s value to positive or negative infinity. The float class provides the methods IsPositiveInfinity, IsNegativeInfinity, and IsInfinity to determine whether a variable is holding one of these special values. The following code shows the preceding example rewritten to deal with values that are too big or too small to fit in a float.

float theFloat;
double theDouble = -1e200;

theFloat = (float)theDouble;
if (float.IsInfinity(theFloat)) ... Do something about it ...

Casting Objects

Converting an object to an ancestor class is a widening conversion. For example, suppose the Student class inherits from the Person class. In that case, all Students are Persons so converting a Student into a Person is a widening conversion. That means the following implicit conversion works.

Student student = new Student();
Person person;

person = student;

A variable of an ancestor class can hold objects of a derived class. For example, a Person variable can hold a Student object because a Student is a type of Person. However, not all Person objects are Students. That means casting a Person to a Student is a narrowing conversion and may or may not succeed depending on whether the Person actually is a Student or something else such as a Teacher or Janitor.

The following code demonstrates two narrowing object conversions.

Person personA = new Student();
Person personB = new Janitor();
Student student;

student = (Student)personA;
student = (Student)personB;

The first conversion casts personA into a Student. Variable personA happens to hold a Person object, so this works.

The second conversion casts personB into a Student. Variable personB happens to hold a Janitor. A Janitor is not a kind of Student so this fails at run time with a System.InvalidCastException.

One way to determine whether a cast from one object type to another is valid is to try it and catch the InvalidCastException if it occurs. Another method is to use the is operator. The is operator determines whether an object is compatible with a given type.

For example, suppose the variable person holds a reference to an object that might be a Student or a Janitor. The following code tests whether that object can be cast into a Student before performing the conversion.

if (personB is Student)
{
    student = (Student)personB;

    // Do something with the Student...
}

Using the as Operator

The as operator provides a shorthand for converting objects from one type to another. The following code converts the value in variable person into a Student and saves it in variable student.

student = person as Student;

This is similar to a cast if the variable person holds a value that can be converted into a Student. If the value cannot be converted into a Student (for example, if it is a Janitor), then the as operator returns the special value null. That leaves the variable student referring to no object.

Casting Arrays

Casting lets you convert values between primitive and object types. It also lets you cast arrays of objects.

For example, in some sense an array of Student objects is also an array of Person objects because a Student is a type of Person. That means a C# program can implicitly convert an array of Student into an array of Person, and it can explicitly convert an array of Person into an array of Student. The following code demonstrates those implicit and explicit conversions.

// Make an array of Students.
Student[] students = new Student[10];

// Implicit cast to an array of Persons.
// (A Student is a type of Person.)
Person[] persons = students;

// Explicit cast back to an array of Students.
students = (Student[])persons;

Parsing

Each of the fundamental data types (except for string) has a Parse method that tries to convert a string into that type. For example, the following code saves the value 112358 in a string variable. It then uses the int class’s Parse method to convert that string into an int.

string text = "112358";
int value = int.Parse(text);

Some of these parsing methods can take additional parameters to control the conversion. For example, the numeric methods can take a parameter that gives the international number style the string should have.

If the string passed to the Parse method doesn’t make sense, the method throws an exception. A program can catch the exception as described in Chapter 9. Alternatively, the code can use a corresponding TryParse method. Each class’s TryParse method attempts to parse a string much as Parse does. Unlike Parse, TryParse returns the parsed value through an output parameter, and the method’s return value is true if parsing was successful and false otherwise. The following code shows how a program might use TryParse.

string text = "112358";
int value;
if (int.TryParse(text, out value))
{
    // Do something with value ...
}

Using System.Convert

The Convert class has a variety of methods that convert values from one data type to another. For example, the following code uses the ToInt64 method to convert the string “61” into a 64-bit integer.

long value = Convert.ToInt64("61");

These methods are easy to understand so they make the code simple to read. Unfortunately, they work with particular data type sizes such as 32- or 64-bit integers rather than with the system’s default integer size, so they may require you to change your code in the future. If a later version of C# assumes that long means 128-bit integer, you may need to update your calls to Convert.ToInt64.

Using System.BitConverter

The System.BitConverter defines methods that convert values to and from arrays of bytes.

The GetBytes method returns an array of bytes representing a value. Methods such as ToInt32 and ToDouble convert values stored in byte arrays back into specific data types.

Often these methods are used to convert values returned by API functions into more usable types. For example, an API function might return two 16-bit integer values packed into the halves of a single 32-bit value. You could use these methods to convert the 32-bit value into an array of 4 bytes and then convert the two pairs of bytes into 16-bit values.

The BitConverter class’s methods are quite specialized, so they are not described further here. For more information, see “BitConverter Class” at msdn.microsoft.com/library/system.bitconverter.aspx.

ToString

The ToString method is so useful it deserves special mention. Every object has a ToString method that returns a string representation of the object. For example, the following code converts the decimal value totalCost into a string and saves the result in the variable totalString.

string totalString = totalCost.ToString();

The value returned by ToString depends on the object. Simple objects such as the primitive data types (byte, int, long, decimal, string, and so on) return their values. More complicated objects often return their class names rather than their values. (You can override a class’s ToString method to make it return something more useful if that makes sense. For example, you could make the Person class’s ToString method return the person’s first and last names.)

The ToString method can take as a parameter a format string to change the way the method formats its result. For example, the following code displays the value of the decimal variable cost with two digits after the decimal point.

MessageBox.Show(cost.ToString("0.00"));

Appendix P, “Date and Time Format Specifiers,” and Appendix Q, “Other Format Specifiers,” describe format specifiers in greater detail.

Scope

A variable’s scope determines which other pieces of code can access it. For example, if you declare a variable inside a method, only code within that method can access the variable. The three possible levels of scope are (in increasing size of scope) block, method, and class.

Block Scope

A block is a series of statements enclosed in braces. If you declare a variable within a block of code, the variable has block scope, and only other code within that block can access the variable. Furthermore, only code that appears after the variable’s declaration can see the variable.

Variables declared in the block’s opening statement are also part of the block. Note that a variable is visible within any subblock contained within the variable’s scope.

For example, consider the following code snippet.

for (int i = 1; i <= 5; i++)
{
    int j = 3;
    if (i == j)
    {
            int sum = i + j;
            Console.WriteLine("Sum: " + sum);
    }
    else
    {
            int product = i * j;
            Console.WriteLine("Product: " + product);
    }

    int k = 123;
    Console.WriteLine("k: " + k);
}

This code uses a for loop with the looping variable i declared in the for statement. The scope of variable i is the block defined by the for loop. Code inside the loop can see variable i, but code outside of the loop cannot.

Inside the loop, the code declares variable j. This variable’s scope is also the for loop’s block.

If i equals j, the program declares variable sum and uses it. This variable’s scope includes only the two lines between the if and else statements.

If i doesn’t equal j, the code declares variable product. This variable’s scope includes only the two lines between the else statement and the closing brace.

The program then declares variable k. This variable also has block scope, but it is available only after it is declared, so the code could not have accessed it earlier in the for loop.

Other code constructs that define blocks include the following:

  • switch statements—All the case statements exist within the block defined by the switch statement.
  • try catch finally statements—The try section, catch sections, and finally section all define separate blocks. Note also that the exception variable used by each catch statement is in the block defined by its catch statement. (That means they can all have the same name.)
  • while loops—Variables declared inside the loop are local to the loop.
  • using statements (not using directives)—Resources acquired by the block and variables declared inside the block are local to the block.

Because block scope is the most restrictive, you should use it whenever possible to reduce the chances for confusion. The section “Restricting Scope” later in this chapter talks more about restricting variable scope.

Method Scope

If you declare a variable inside a method but not within a block, the variable is visible to any code inside the procedure that follows the declaration. The variable is not visible outside of the method. In a sense, the variable has block scope where the block is the method.

A method’s parameters also have method scope. For example, in the following code, the scope of the order and item parameters is the AddOrderItem method.

public void AddOrderItem(Order order, OrderItem item)
{
    order.OrderItems.Add(item);
}

Class Scope

A variable with class (or structure) scope is available to all code in its class (or structure) even if the code appears before the variable’s declaration. For example, the following code works even though the DisplayLoanAmount method is declared before the LoanAmount variable that it displays.

public class Lender
{
    public void DisplayLoanAmount()
    {
        MessageBox.Show(LoanAmount.ToString());
    }

    private decimal LoanAmount;
    ...
}

Depending on its accessibility keyword, the variable may be visible outside of the class. For example, if you declare the variable with the public keyword, it is visible to all code outside of the class. See the section “Accessibility” earlier in this chapter for more information.

Restricting Scope

There are several reasons why you should give variables the most restrictive scope possible that still lets them do their jobs.

Limited scope keeps the variable localized, so programmers cannot use the variable incorrectly in far off code that is unrelated to the variable’s main purpose.

Having fewer variables with wide scope (such as public) means programmers have less to remember when they work on the code. They can concentrate on their current work, rather than worry about what an object’s p and q fields mean.

Limiting scope keeps variables closer to their declarations, so it’s easier for programmers to check the declaration. One of the best examples of this situation is when a for loop declares its looping variable right in the for statement. A programmer can easily see that the looping variable is an integer (for example) without scrolling to the top of the method hunting for its declaration. It is also easy to see that the variable has block scope, so other variables with the same names can be used outside of the loop.

Finally, limited scope means a programmer doesn’t need to worry about whether a variable’s old value will interfere with the current code or whether the final value after the current code exits will later interfere with some other code This is particularly true for looping variables. If a program declares variable i at the top of a method and then uses it many times in various loops, you might need to do a little thinking to be sure the variable’s past values won’t interfere with new loops. If you declare i separately in each for statement, each loop has its own version of i, so there’s no way they can interfere with each other.

Parameter Declarations

A parameter declaration for a method defines the names and types of the parameters passed into it. Parameters always have method scope. C# creates parameter variables when a method begins and destroys them when the method ends. The method’s code can access the parameters, but code outside of the method cannot.

For example, the following method takes an integer named id as a parameter. Code within the method can access id, and code outside of the method cannot.

public void DisplayEmployee(int id)
{
    ...
}

A parameter’s basic scope is straightforward (method scope), but parameters have some special features that complicate the situation. Although this isn’t exactly a scoping issue, it’s related closely enough to scope that it’s worth covering here.

There are three ways you can pass values into a method: by value, by reference, and for output.

By Value

By default a parameter’s value is passed into the method by value. That means the method receives a copy of the parameter’s value. If the method modifies the parameter, it modifies only the copy, so the value in the calling code remains unchanged.

For example, consider the following code.

private void DoubleTest()
{
    int value = 10;
    DoubleIt(value);
    Console.WriteLine("DoubleTest: " + value.ToString());
}

private void DoubleIt(int number)
{
    number *= 2;
    Console.WriteLine("DoubleIt: " + number.ToString());
}

The DoubleTest method creates variable value and initializes it to 10. It then calls the DoubleIt method, passing it value as an argument. (In the calling code, a value passed to a method is called an argument.)

The DoubleIt method receives the value as the parameter number. (When a method receives a value, it is called a parameter.) Notice that the parameter’s name doesn’t need to be the same as the argument. Actually, often the argument isn’t a simple variable as it is in this case. The DoubleTest method could have used an arithmetic statement as an argument as in DoubleIt(value / 3).

The DoubleIt method doubles the value of its parameter and displays the result in the Console window. Control then returns to the DoubleTest method.

Because the parameter was passed to the DoubleIt method by value, the original variable value in the DoubleTest method is unchanged.

If you run this code, the following text appears in the Console window.

DoubleIt: 20
DoDouble: 10

By Reference

One alternative to passing a value into a method by value is to pass it by reference. In that case the method receives a reference to the argument’s value, not a copy of the value. That means if the method changes the parameter, the argument in the calling code is also changed.

To pass a value by reference, add the keyword ref before the parameter declaration. If a parameter is declared with the ref keyword, the argument in the calling code must also include the ref keyword to make it obvious that you are passing the value by reference. That will hopefully prevent you from being surprised when the calling code modifies the argument’s value. (Actually, you wouldn’t declare a parameter with the ref keyword unless you intended the method to modify it.)

The following code shows the previous example with the method’s parameter passed by reference. The ref keywords are shown in bold.

private void DoubleTest()
{
    int value = 10;
    DoubleIt(ref value);
    Console.WriteLine("DoubleTest: " + value.ToString());
}

private void DoubleIt(ref int number)
{
    number *= 2;
    Console.WriteLine("DoubleIt: " + number.ToString());
}

If you run the code now, the following text appears in the Console window.

DoubleIt: 20
DoDouble: 20

This time the DoubleIt method doubled its parameter, and the change was reflected in the value argument in the calling code.

For Output

The final way you can pass a value into a method uses the out keyword. This keyword means the parameter is intended to be an output parameter. The argument is passed into the method by reference so the method can set its value. The method does not assume the value has been initialized before it is passed into the method, and the method assigns a value to the parameter before it returns.

As is the case for the ref keyword, if you add the out keyword to a parameter declaration, you must also include it with the argument. The following code shows the previous example modified to use the out keyword.

private void DoubleTest()
{
    int value;
    DoubleIt(out value);
    Console.WriteLine("DoubleTest: " + value.ToString());
}

private void DoubleIt(out int number)
{
    number = 50;
    Console.WriteLine("DoubleIt: " + number.ToString());
}

Because the DoubleIt method doesn’t require that its parameter be initialized, the DoubleTest method does not initialize its value variable. The DoubleIt method sets the parameter’s value and displays it. Because the parameter is declared out, the value returns to the calling code, so in the DoubleTest method value becomes 50.

The following text shows the result.

DoubleIt: 50
DoubleTest: 50

Unusual Circumstances and Exceptions

Even if you know how to pass arguments by value, by reference, and for output, there are situations that can be confusing. Recall that some variables are value types and others are reference types. When you pass a value type variable by value, the method receives a copy of the value and all is as you would expect.

If you pass a reference variable by value, the method receives a copy of the reference, not a copy of the reference’s data. That means the method cannot change the reference (because it was passed by value) but it can change the data associated with the reference.

For example, consider the following method.

private void SetCity(Person person)
{
    person.City = "Bugsville";
    person.Zip = 12345;
}

This method takes a Person parameter passed by value, changes the object’s City and Zip values, and ends.

If Person is a class, it is a reference type so the method receives a copy of the reference. That means the parameter refers to the same object as the argument in the calling code, so any changes to the object’s properties are also changes to the original object.

In the calling code, the Person object hasn’t changed but its City and Zip values have.

Now consider the following version of the SetCity method.

private void SetCity(Person person)
{
    person = new Person() { City = "Programmeria", Zip = 54321 };
}

This version sets the person parameter to a new Person object. Because the parameter was passed by reference, however, the calling code’s Person object isn’t changed. You can make the calling code receive the new value by declaring the parameter ref or out.

A similar phenomenon occurs when you pass arrays into a method. If the array is passed by value, the method can change its values, but if it sets the parameter equal to a new array, the change does not return to the calling code. If the array parameter is declared ref or out, the method can set the parameter equal to a new array, and the change returns to the calling code.

Chapter 6 has more to say about methods.

Properties

In C# a property is similar to a field in a class except it is implemented by accessor methods instead of as a simple variable. A property’s get and set accessors allow the program to get and set the property’s value.

The following code shows a simple Name property.

private string _Name;
public string Name
{
    get
    {
        return _Name;
    }
    set
    {
        _Name = value;
    }
}

The code begins by declaring a private string variable _Name. Sometimes this private variable is called the property’s backing field because it holds the value for the property.

The next statement begins the definition of the public Name property. The get accessor simply returns the value in the _Name variable.

The set accessor sets the _Name variable equal to the special value parameter. The value parameter is implicitly declared for the set accessor. It acts like any other parameter except you don’t need to define it.

A program could use these methods exactly as if there were a public field. For example, if this code is in the Employee class, the following code shows how a program could set and then get the Name property for an Employee object named emp.

emp.Name = "Rod Stephens";
Console.WriteLine(emp.Name);

You might want to use properties instead of public fields for several reasons. First, the accessors give you extra control over how the program stores and retrieves the property’s value. For example, the set accessor could use code to validate the value before saving it in the backing field. The code could verify that a postal code or phone number has the proper format and throw an exception if the value is badly formatted.

You can also set breakpoints in property methods. Suppose your program is crashing because a piece of code is setting a value incorrectly. The crash doesn’t occur right away, so the value may have been set long before the crash. If you implement the value with a property, you can set a breakpoint in the set accessor and stop whenever the program sets the value.

Properties also enable you to set and get values in formats other than those you want to actually use to store the value. For example, the following code defines a Name property that saves a full name in first and last name variables.

private string _FirstName, _LastName;
public string Name
{
    get
    {
        return _FirstName + " " + _LastName;
    }
    set
    {
        _FirstName = value.Split(' ')[0];
        _LastName = value.Split(' ')[1];
    }
}

Here the get accessor returns the concatenation of the _FirstName and _LastName fields. The set accessor uses the string class’s Split method to split the new value into two pieces delimited by a space character. It saves the first part of the value in _FirstName and the second part in _LastName.

Finally, you can use properties to create read-only and write-only values. The following code shows how to make a read-only NumEmployees property method and a write-only NumCustomers property method. (Write-only property methods are unusual but legal.)

private int _NumEmployees;
public int NumEmployees
{
    get
    {
        return _NumEmployees;
    }
}

private int _NumCustomers;
public int NumCustomers
{
    set
    {
        _NumCustomers = value;
    }
}

To make a property read-only or write-only, the code simply omits the accessor that it doesn’t need. The properties’ backing fields are still visible to code inside the class so that code can get and set the values as needed.

A backing field isn’t the only way you can store a property’s value. For example, you could store the value in a database, text file, or configuration setting. Still, backing fields are quite popular. They’re so popular that C# provides auto-implemented properties that use backing fields behind the scenes.

To create an auto-implemented property, simply omit the body of the accessors as in the following example.

public string Name { get; set; }

The advantage of auto-implemented properties is that you don’t need to write as much code. The disadvantage is that you can’t set breakpoints in the accessors.

Enumerations

An enumeration (also called an enumerated type or simply an enum) is a discrete list of specific values called enumerators. You define the enumeration and the values allowed. Later, you can declare a variable of the enumeration’s type so it can take only those values.

For example, suppose that you build a large application where users can have one of three access levels: clerk, supervisor, and administrator. You could define an enumeration named AccessLevels that contains the enumerators Clerk, Supervisor, and Administrator. Now, if you declare a variable to be of type AccessLevels, C# allows the variable to take only those values.

The following code shows a simple example.

// Define the access level values.
public enum AccessLevels
{
    Clerk,
    Supervisor,
    Administrator,
}

// The user's access level.
private AccessLevels Level;

// Set supervisor access level.
public void MakeSupervisor()
{
    Level = AccessLevels.Supervisor;
}

This code defines the AccessLevels type and declares the variable Level of the type. Later the MakeSupervisor method sets Level to the value AccessLevels.Supervisor. (Note that the value is prefixed with the enumerated type’s name.)

The syntax for declaring an enumerated type is as follows:

«attributes0» «accessibility» name
«: type»
{
    «attributes1» name1 «= value1»,
    «attributes2» name2 «= value2»,
    ...
}

The pieces of this declaration are

  • attributes0—Attributes that specify extra properties for the enumeration. See the section “Attributes” earlier in this chapter for more information.
  • accessibility—This determines which code can access the variable. See the section “Accessibility” earlier in this chapter for more information.
  • name—The name you want to give the enumeration.
  • : type—All enumerations are stored internally as integer values. By default, an enumeration’s type is int. You can use this part of the declaration to change the underlying type to byte, sbyte, short, ushort, int, uint, long, or ulong. (Any integer type except char.)
  • attributes1, attributes2—Attributes that specify extra properties for the enumerators.
  • name1, name2—The names of the enumerators.
  • value1, value2—The integer value that should be used to store this enumerator. By default, the first enumerator is represented by 0 and the values of subsequent enumerators are increased by 1.

The following code provides a more complicated example.

public enum Meal : sbyte
{
    Breakfast = 1,
    Lunch = Breakfast * 10,
    Dinner = 100,
    Supper = Dinner,
}

This code defines an enumeration named Meal that stores its enumerators with the sbyte data type. The enumerator Breakfast is represented by the value 1, Lunch is represented by 10 times the value of Breakfast, and Dinner is represented by the value 100.

The enumerator Supper is defined to be the same as the enumerator Dinner. Both are stored as the value 100 internally, so the program cannot distinguish between the two values.

Usually, all that’s important about an enumeration is that its values are distinct, so you don’t need to change the underlying data type or initialize the values.

Normally, a program should set a variable’s value to one of the allowed enumerators as in the statement Level = AccessLevels.Supervisor. If for some reason you need to set the value to a calculated result, you can cast the result into the enumeration’s type. For example, the following code sets the Meal variable food to the value 1 cast into a Meal.

Meal food = (Meal)1;

You can declare an enumeration inside a class or namespace.

If you define an enumeration inside a namespace, code within the namespace can refer to it by its name. If the enumeration’s accessibility level permits, code outside the namespace can refer to it by giving the namespace and the enumeration. For example, suppose the Meal enumeration is defined in the FoodStuff namespace. Then a piece of code in some other namespace could use the enumeration as in the following code.

FoodStuff.Meal meal = FoodStuff.Meal.Breakfast;

If you define an enumeration inside a class and its accessibility permits, code outside of the class can refer to the enumeration by including the class name. If the code is in another namespace, it should add that, too.

For example, suppose the FoodStuff namespace contains the Oven class, which defines the Meal enumeration. The following code shows how a method inside the Oven class could use the enumeration.

Meal meal = Meal.Breakfast;

The following code shows how a method outside of the Oven class but inside the FoodStuff namespace could use the enumeration.

Oven.Meal meal = Oven.Meal.Breakfast;

The following code shows how a method in another namespace could use the enumeration.

FoodStuff.Oven.Meal meal = FoodStuff.Oven.Meal.Breakfast;

Enumerations have several advantages. First, they encourage you to use meaningful values instead of “magic numbers” in your code. Instead of using the potentially confusing value 1, you can use the value Breakfast.

Second, enumerations allow Visual Studio to provide IntelliSense help. If you type Meal meal = IntelliSense provides the choice Meal. If you select it and type a period, IntelliSense displays the possible enumerators.

A final benefit of enumerations is that they provide a ToString method that returns the textual name of the value. For example, the following code displays the message “Breakfast.”

Meal meal = Meal.Breakfast;
Console.WriteLine(meal.ToString());

(Actually the Console.WriteLine method calls the ToString method for any object that it is passed, so you could write the second statement as Console.WriteLine(meal).)

If you have a variable that can take only a fixed number of values, you should probably make it an enumerated type. Also, if you discover that you have defined a series of constants to represent related values, you should consider converting them into an enumerated type. Then you can gain the benefits of the improved type checking and IntelliSense.

Nullable Types

Most relational databases have a concept of a null data value. A null value indicates that a field does not contain any data. It lets the database distinguish between valid zero or blank values and nonexisting values. For example, a null bank balance would indicate that there is no known balance, whereas a 0 would indicate that the balance was 0.

You can create a nullable variable in C# by adding a question mark after the variable’s data type. The following code declares a nullable int variable.

int? count;

To make a nullable variable “null,” set it equal to null. To give a variable a non-null value, simply set it equal to the value. The following code sets variable count to null and then 1337.

int? count;
count = null;
count = 1337;

To determine whether a nullable variable contains a value, either use its HasValue property or simply compare it to null. The following code determines whether the variable count holds a value in a couple different ways.

if (count.HasValue) Console.WriteLine("count = " + count);
else Console.WriteLine("count = null");
if (count != null) Console.WriteLine("count = " + count);
if (count == null) Console.WriteLine("count = null");

The first line of code uses the variable’s HasValue property. If count has a value, the code displays it.

The else statement on the next line displays “count = null” if the variable has no value.

The next two lines use != and == to compare the variable to the value null.

The syntax used by nullable variables makes them remarkably easy to use and understand. There’s only one slightly complex issue: null propagation. When you perform a calculation with a nullable variable that holds no value, its “nullness” propagates into the result. For example, if a nullable integer contains no value, it probably doesn’t make sense to add another number to it. (What is null + 11?)

If any of the operands in an expression contains a null value, the result is a null value. Any time you need to use a nullable value, you should first check to see whether it is null or a usable value.

Delegates

A delegate is a special data type that refers to a method. The method can be an instance method provided by an object or a static method defined by a class. Like other types such as classes and enumerations, delegates can be defined in namespaces and classes but not inside methods.

A delegate variable acts as a pointer to a method. Delegate variables are sometimes called type-safe function pointers.

The delegate keyword defines a delegate type and specifies the parameters and return type of the method to which the delegate refers.

After you have defined a delegate type, you can declare variables that are of that type. You can set those variables equal to methods that match the delegate’s parameters and return value.

For example, the following code defines a delegate type named ShowMessageType. This delegate represents methods that take a single string as a parameter and that don’t return any result.

private delegate void ShowMessageType(string msg);

The following defines a ShowInConsoleWindow method, which simply displays a message in the Console window.

// Display a message in the Console window.
private void ShowInConsoleWindow(string message)
{
    Console.WriteLine(message);
}

The ShowInConsoleWindow method takes a single string parameter and returns no result, so it matches the ShowMessageType delegate.

The following code creates a delegate variable and makes it refer to the ShowInConsoleWindow method.

// Create a delegate variable holding a
// reference to the ShowInConsoleWindow method.
ShowMessageType say = ShowInConsoleWindow;

Now that the delegate variable refers to a method, the code can invoke that method by using the variable. The following code shows how a program can invoke the method referred to by the say variable.

// Invoke the method.
say("Hello");

Delegate variables are similar to any other kind of variable and the same rules apply to them. You can change their values, pass them as arguments into methods, and use them as members of classes and structures. (Chapter 12 says more about creating classes and structures.)

For a more interesting example, consider the following ProcessApplicant method.

// Process a job applicant.
private void ProcessApplicant(Person applicant, ShowMessageType status)
{
    status("Processing applicant " + applicant.Name);

    // Process the applicant ...

    status("Done processing applicant " + applicant.Name);
}

This method takes as parameters a Person object to process and a method of type ShowMessageType. It starts by calling the method to display status information saying it is starting to process the applicant. The method then does whatever it needs to do to process the applicant. It finishes by calling the status method again to say it is done processing the applicant.

Depending on what you want to do with the status information, you can pass different status methods to ProcessApplicant. For example, the following code passes ProcessApplicant the method ShowInConsoleWindow to make the status messages appear in the Console window.

ProcessApplicant(person, ShowInConsoleWindow);

The following code uses the ShowInMessageBox method to make the status messages appear in message boxes.

ProcessApplicant(person, ShowInMessageBox);

You could define other methods to write status messages into a log file, save messages in a database, e-mail messages to someone, or ignore the messages completely. The main program can pass any of these methods into the ProcessApplicant method to make it handle the messages differently.

Summary

Two of the most important things you control with a variable declaration are its data type and its visibility. Visibility combines scope (the piece of code that contains the variable such as a for loop, method, or class), accessibility (the code that is allowed to access the variable determined by keywords such as private, public, and protected), and lifetime (when the variable has been created and not yet destroyed).

To avoid confusion, explicitly declare the data type whenever possible and use the most limited scope possible for the variable’s purpose.

Code that uses LINQ (described in Chapter 8) complicates matters somewhat. The results of LINQ expressions often have weird data types, and it can be extremely difficult to use those exact types. In that case you can use the var data type to let the program determine the true data type at runtime.

Now that you know how to declare variables, you can learn how to combine them. Chapter 5, “Operators,” explains the symbols (such as +, *, and %) that you can use to combine variables to produce new results.

Exercises

  1. Suppose the variable student has type Student, the variable person has type Person, and the Student class inherits from the Person class. Use an if statement and the is operator to write code that is equivalent to the following statement but without the as operator.
    student = person as Student;
  2. Write a statement that declares an int array named fibonacci that holds values 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, and 89.
  3. Write a statement that declares an 8 8 array named board that holds instances of the Person class where each entry is initially null.
  4. Repeat Exercise 2 using an array of arrays.
  5. Create a Person class with text fields FirstName, LastName, Street, City, and State. Also give the class a numeric Zip field that is just big enough to hold five-digit ZIP codes. Define the fields so all code can see them.
  6. Write a statement that declares a 2 2 array containing objects of the Person class you created for Exercise 5. Initialize them to hold the names Ann Archer, Ben Baker, Cindy Cant, and Dan Deevers.
  7. Write a statement that declares and initializes a three-dimensional 2 2 3 array of strings where each entry gives its position in the array. For example, values[0, 1, 1] should hold the value “011.”
  8. Write a method that takes a ref parameter. What happens if you try to pass in an argument that has not been initialized in the calling code?
  9. Modify the method you wrote for Exercise 8 so that it uses an out parameter instead of a ref parameter. What happens if the calling code does not initialize the argument before passing it into the method? What if it does initialize the argument?
  10. What happens if you modify the method you wrote for Exercise 8 so that it doubles its parameter when it starts?
  11. What happens if you modify the method you wrote for Exercise 8 so that it does nothing with its parameter?
  12. What happens if you try to pass an expression such as 12 * 3 into a method that takes an int parameter by reference?
  13. Make an Oven class that has TempFahrenheit and TempCelsius properties to get and set the temperature in degrees Fahrenheit and Celsius, respectively. Store the temperature in a single private variable. (You decide whether to store the temperature in Fahrenheit or Celsius.) Use the following equations to convert from one to the other.

    F = C 9/5 + 32

    C = (F - 32) 5/9

  14. Make two methods Combine1 and Combine2 that both take two ints as parameters and return a string. The first should return the values concatenated as in “(12, 34).” The second should return them concatenated as in “R12C34.”

    Next declare a delegate variable named combiner to hold a reference to these methods. Use combiner to test the methods as in the following code.

    combiner = Combine1;
    Console.WriteLine(combiner(1, 2));
    combiner = Combine2;
    Console.WriteLine(combiner(1, 2));
..................Content has been hidden....................

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