Using Types - MCSD Certification Toolkit (Exam 70-483): Programming in C# (2013)

MCSD Certification Toolkit (Exam 70-483): Programming in C# (2013)

Chapter 4

Using Types

What You Will Learn in This Chapter

· Converting values from one data type to another

· Widening, narrowing, implicit, and explicit conversions

· Casting

· Converting values with help methods and classes

· Manipulating strings

· Formatting values

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

You can find the code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=1118612094 on the Download Code tab. The code is in the chapter 04 download and individually named according to the names throughout the chapter.

Chapter 3, “Working with the Type System,” introduces the C# type system. It explains how to create value types (data structures and enumerations) and reference types (classes). It also explains encapsulation, and generic types and methods. This chapter continues the discussion of types by explaining how to convert between different types, such as converting an int or float into a string for display to the user. It explains how to use types to interact with unmanaged code. It also explains how to manipulate strings to perform such operations as determining whether a string begins with a given prefix and extracting substrings.

Table 4-1 introduces you to the exam objectives covered in this chapter.

Table 4-1: 70-483 Exam Objectives Covered in This Chapter

Objective

Content Covered

Create and Use Types

Create types. This includes boxing and unboxing value types, converting and casting between value types, handling dynamic types, and ensuring interoperability with unmanaged code.
Manipulate strings. This includes understanding string methods, searching strings, formatting strings, and manipulating strings by using the StringBuilder, StringWriter, and StringReader classes.

Converting Between Types

Many programs must convert data from one type to another. For example, a graphing program might use the Math.Sin and Math.Cos functions to calculate values for a graph, and then use the Graphics object’s DrawLines method to draw the graph. However, Math.Sin and Math.Cos return values asdoubles, and DrawLines represents points as floats or ints. At some point the program must convert the double values into floats or ints.

In addition to converting one data type into another, a program may need to convert text entered by the user into other data types such as ints, floats, or DateTimes so that it can manipulate those values.

The following sections discuss various ways a C# program can convert one data type to another:

· Casting

· Using the as operator

· Parsing

· Using System.Convert

· Using System.BitConverter

Using Widening and Narrowing Conversions

You can categorize conversions as either widening or narrowing. The code that performs a conversion can also be implicit or explicit.

In a widening conversion, the destination data type can hold any value provided by the source data type. In a narrowing conversion, the destination data type cannot hold all possible values held by the source data type.

For example, an int variable can hold integer values between -2,147,483,648 and 2,147,483,647. A short variable can hold integer values only between -32,768 and 32,767. That means converting from a short to an int is a widening conversion because an int can hold any value that a shortcan hold. A widening conversion always succeeds.

In contrast, converting from an int to a short is a narrowing conversion because a short cannot hold every possible value in an int. That doesn’t mean a narrowing conversion from an int to a short will always fail, however. If an int variable happens to hold a value that can fit in a short, such as 100 or -13,000, the conversion succeeds. If the int holds a value that won’t fit in a short, such as 70,000, the conversion fails.

By default, C# does not throw an exception if a narrowing conversion results in an error for integer or floating point types. For integers, it truncates the result and continues merrily along as if nothing had gone wrong. For floating point types, the program sets the result variable’s value toInfinity if the value doesn’t fit and again continues executing.

You can make C# throw an exception for invalid narrowing integer conversions in a couple ways. First, you can use a checked block as shown in the following code. (The cast operator (short) is described in the section “Casting” later in this chapter.)

checked

{

int big = 1000000;

short small = (short)big;

}

Within the checked block, the program throws an OverflowException if the conversion from the intbig to the shortsmall fails.

NOTE A checked block does not protect code inside methods called within the block. For example, suppose the code inside a checked block calls the CalculateTaxes method. In that case the checked block does not protect the CalculateTaxes method if it performs a narrowing conversion. If CalculateTaxes tries to make a narrowing conversion that fails, the program does not throw an exception.

Figure 4-1: Use the Advanced Build Settings dialog to make the program check for integer overflow and underflow.

image

You can also make a program throw exceptions for invalid integer conversions by opening the project’s Properties page, selecting the Build tab, and clicking the Advanced button to display the Advanced Build Settings dialog (see Figure 4-1). Make sure the Check For Arithmetic Overflow/Underflow box is checked, and click OK.

The checked block and Check for Arithmetic Overflow/Underflow setting throw exceptions only for integer operations. If a program saves a double precision value into a float variable, the code must explicitly check the result to see if it is set to Infinity to detect an overflow. The code should probably also check for NegativeInfinity to catch underflow conditions.

COMMON MISTAKES: Performing Narrowing Conversions That Result in Integer Overflows

Beginning programmers often don’t realize that the program won’t complain if it performs a narrowing conversion that results in an integer overflow or underflow. To avoid confusing bugs, make the program throw an exception in those cases.

The following code uses the float type’s IsInfinity method to determine whether the narrowing conversion caused an overflow or underflow:

double big = -1E40;

float small = (float)big;

if (float.IsInfinity(small)) throw new OverflowException();

COMMON MISTAKES: Performing Floating Point Conversions That Result in Overflows

Beginning programmers often don’t realize that the program will continue running if a floating point conversion or calculation results in an overflow or underflow. To avoid bugs, check the result for Infinity and NegativeInfinity.

Using Implicit and Explicit Conversions

An implicit conversion is one in which the program automatically converts a value from one data type to another without any extra statements to tell it to make the conversion. In contrast, an explicit conversion uses an additional operator or method such as a cast operator (described in the next section) or a parsing method (described in the section “Parsing Methods”) to explicitly tell the program how to make the conversion.

Because narrowing conversions may result in a loss of data, a C# program won’t perform a narrowing conversion automatically, so it won’t enable an implicit narrowing conversion. The code must explicitly use some sort of conversion operator or method to make it clear that you intend to perform the conversion, possibly resulting in loss of data.

In contrast, a widening conversion always succeeds, so a C# program can make widening conversions implicitly without using an explicit conversion operator or method. You can use a conversion operator, but you are not required to do so.

The following code shows examples of implicit and explicit conversions:

// Narrowing conversion so explicit conversion is required.

double value1 = 10;

float value2 = (float)value1;

// Widening conversion so implicit conversion is allowed.

int value3 = 10;

long value4 = value3;

For reference types, converting to a direct or indirect ancestor class or interface is a widening conversion, so a program can make the conversion implicitly. The following section includes more about converting reference types.

Casting

A cast operator explicitly tells the compiler that you want to convert a value into a particular data type. To cast a variable into a particular type, place the type surrounded by parentheses in front of the value that you want to convert.

For example, the following code initializes the double variable value1, casts it into the float data type, and then saves the new float value in variable value2.

double value1 = 10;

float value2 = (float)value1;

NOTE Casting a floating point value into an integer data type causes the value to be truncated. For example, the statement (int)10.9 returns the integer value 10. If you want to round the value to the nearest integer instead of truncating it, use the System.Convert class’s ToInt32method (described in the section “System.Convert” later in this chapter) or the Math.Round method.

As the previous section mentioned, converting a reference type to a direct or indirect ancestor class or interface is a widening conversion, so a program can make the conversion implicitly. For example, if the Employee class is derived from the Person class, you can convert an Employee object into a Person object:

Employee employee1 = new Employee();

Person person1 = employee1;

Converting a reference value to an ancestor class or interface does not actually change the value; it just makes it act as if it were of the new type. In the previous example, person1 is a Person variable, but it references an Employee object. The code can use the variable person1 to treat the object as a Person, but it is still an Employee.

Because person1 is actually an Employee, you can convert it back to an Employee variable:

Employee employee1 = new Employee();

Person person1 = employee1;

Person person2 = new Employee();

// Allowed because person1 is actually an Employee.

Employee employee2 = (Employee)person1;

Converting from Person to Employee is a narrowing conversion, so the code needs the (Employee) cast operator.

This kind of cast operator enables the code to compile, but the program throws an InvalidCastException at run time if the value is not actually of the appropriate type. For example, the following code throws an exception when it tries to cast a true Person object into an Employee:

Person person2 = new Person();

// Not allowed because person2 is a Person but not an Employee.

Employee employee3 = (Employee)person2;

Because programs often need to cast reference data from one class to a compatible class, as shown in the previous code, C# provides two operators to make that kind of casting easier: is and as.

The is Operator

The is operator determines whether an object is compatible with a particular type. For example, suppose the Employee class is derived from Person, and the Manager class is derived from Employee. Now suppose the program has a variable named user and it must take special action if that variable refers to an Employee but not to a Person. The following code uses the is operator to determine whether the Person variable refers to an Employee and takes a special action:

if (user is Employee)

{

// Do something with the Employee...

...

}

If the is operator returns true, indicating the variable user refers to an Employee, the code takes whatever action is necessary.

The is operator returns true if the object is compatible with the indicated type, not just if the object actually is of that type. The previous code returns true if user refers to an Employee, but also returns true if user refers to a Manager because a Manager is a type of Employee. (Manager was derived from Employee.)

The as Operator

The previous code takes special action if the variable user refers to an object that has a type compatible with the Employee class. (In this example, that means user is an Employee or Manager.) Often the next step is to convert the variable into a more specific class before treating the object as if it were of that class. The following code casts user into an Employee, so it can treat it as an Employee:

if (user is Employee)

{

// The user is an Employee. Treat is like one.

Employee emp = (Employee)user;

// Do something with the Employee...

...

}

The as keyword makes this conversion slightly easier. The statement object as Class returns the object converted into the indicated class if the object is compatible with that class. If the object is not compatible with the class, the statement returns null.

The following code shows the previous example rewritten to use the as operator:

Employee emp = user as Employee;

if (emp != null)

{

// Do something with the Employee...

...

}

NOTE Whether you use the earlier version that uses is, or this version that uses as, is largely a matter of personal preference.

One situation in which the as operator is particularly useful is when you know a variable refers to an object of a specific type. For example, consider the following RadioButton control’s CheckedChanged event handler:

// Make the selected RadioButton red.

private void MenuRadioButton_CheckedChanged(object sender, EventArgs e)

{

RadioButton rad = sender as RadioButton;

if (rad.Checked) rad.ForeColor = Color.Red;

else rad.ForeColor = SystemColors.ControlText;

}

This event handler is assigned to several RadioButtons’ CheckedChanged events, but no matter which control is clicked, you know that the sender parameter refers to a RadioButton. The code uses the as operator to convert sender into a RadioButton, so it can then use the RadioButton’s Checkedand ForeColor properties.

Casting Arrays

As the previous sections explained, casting enables you to convert data on one type to another compatible type. If the conversion is widening, you don’t need to explicitly provide a cast operator such as (int). If the conversion is narrowing, you must provide a cast operator. These rules hold for value types (such as converting between int and long) and reference types (such as converting between Person and Employee).

These rules also hold for arrays of reference values. Even the is and as operators work for arrays of reference values.

Suppose the Employee class is derived from the Person class, and the Manager class is derived from the Employee class. The CastingArrays example program uses the following code to demonstrate the casting rules for arrays of these classes:

// Declare and initialize an array of Employees.

Employee[] employees = new Employee[10];

for (int id = 0; id < employees.Length; id++)

employees[id] = new Employee(id);

// Implicit cast to an array of Persons.

// (An Employee is a type of Person.)

Person[] persons = employees;

// Explicit cast back to an array of Employees.

// (The Persons in the array happen to be Employees.)

employees = (Employee[])persons;

// Use the is operator.

if (persons is Employee[])

{

// Treat them as Employees.

...

}

// Use the as operator.

employees = persons as Employee[];

// After this as statement, managers is null.

Manager[] managers = persons as Manager[];

// Use the is operator again, this time to see

// if persons is compatible with Manager[].

if (persons is Manager[])

{

// Treat them as Managers.

//...

}

// This cast fails at run time because the array

// holds Employees not Managers.

managers = (Manager[])persons;

This code follows the previous discussion of casting in a reasonably intuitive way. The code first declares and initializes an array of Employee objects named employees.

It then defines an array of Person objects named persons and sets it equal to employees. Because Employee and Person are compatible types (one is a descendant of the other), this cast is potentially valid. Because it is a widening conversion (Employee is a type of Person), this can be an implicit cast, so no cast operator is needed and the cast will succeed.

Next, the code casts the persons array back to the type Employee[] and saves the result in employees. Again these are compatible types, so the cast is potentially valid. This is a narrowing conversion (Employees are Persons but not all Persons are Employees) so this must be an explicit conversion and the (Employee[]) cast operator is required.

The code then uses the is operator to determine whether the persons array is compatible with the type Employee[]. In this example, persons holds a reference to an array of Employee objects, so it is compatible and the program executes whatever code is inside the if statement. (This makes sense now but there’s a counterintuitive aspect to this that is discussed shortly.)

Next, the program uses the as operator to convert persons into the Employee[] array employees. Because persons can be converted into an array of Employees, this conversion works as expected.

The code then uses the as operator again to convert persons into the Manager[] array managers. Because persons holds Employees, which cannot be converted into Managers, this conversion fails, so the variable managers is left equal to null.

The program then uses the is operator to see if persons can be converted into an array of Managers. Again that conversion won’t work, so the code inside this if block is skipped.

Similarly, the explicit cast that tries to convert persons into an array of Managers fails. When it tries to execute this statement, the program throws an InvalidCastException.

COMMON MISTAKES: Casting Doesn’t Make a New Array

All of this makes sense and fits well with the earlier discussion of casting and implicit and explicit conversions. However, there is one counterintuitive issue related to casting arrays. When you cast an array to a new array type, the new array variable is actually a reference to the existing array not a completely new array.

That is consistent with the way C# works when you set two array variables equal to each other for value types. For example, the following code makes two integer array variables refer to the same array:

int[] array1, array2;

array1 = new int[10];

array2 = array1;

This code declares two array variables, initializes array1, and then makes array2 refer to the same array. If you change a value in one of the arrays, the other array contains the same change because array1 and array2 refer to the same array.

This can cause confusion if you’re not careful. If you want to make a new array instead of just a new way to refer to an existing array, use Array.Copy or some other method to copy the array.

Figure 4-2: When you cast an array of reference values, the new variable still refers to the original array.

image

Now back to arrays of references. When the code sets the persons array equal to the employees array, persons refers to the same array as employees. It treats the objects inside the array as Persons instead of Employees, but it is not a new array.

You can see this in Figure 4-2 where IntelliSense is showing the values in the persons array right after setting persons equal to employees. At the top level, IntelliSense shows that persons is equal to CastingArrays.Form1.Employee[10], which is an array of 10 Employee objects. When persons is expanded, IntelliSense treats each of its members as if they were Person objects. That is possible because an Employee is a type of Person so, even though the array holds Employees, the persons array can treat them as if they were Persons.

Knowing that persons is actually a disguised reference to an array of Employee objects, it makes sense that the following statement fails:

persons[0] = new Person(0);

This code tries to save a new Person object in the persons array. The persons array is declared as Person[] so you might think this should work but actually persons currently refers to an array of Employee. You cannot store a Person in an array of Employee (because a Person is not a type ofEmployee), so this statement throws an ArrayTypeMismatchException at run time.

COMMON MISTAKES: Casting Reference Arrays into a New Type

Remember that a reference array cast into a new type doesn’t actually have that new type. You can just treat the objects it holds as if they are of the new type.

In summary, you can cast arrays of references in a reasonably intuitive way. Just keep in mind that the underlying values still have their original types even if you’re treating them as something else, as this example treats Employee objects as Person objects.

Converting Values

Casting enables a program to convert a value from one type to another compatible type, but sometimes you may want to convert a value from one type to an incompatible type. For example, you may want to convert the string value 10 to the int value 10, or you might want to convert the stringvalue True to the bool value true. In cases such as these, casting won’t work.

To convert a value from one type to an incompatible type, you must use some sort of helper class. The .NET Framework provides three main methods for these kinds of conversions:

· Parsing methods

· System.Convert

· System.BitConverter

Each of these methods is described in more detail in the following sections.

Parsing Methods

Each of the primitive C# data types (int, bool, double, and so forth) has a Parse method that converts a string representation of a value into that data type. For example, bool.Parse takes as an argument a string representing a boolean value such as true and returns the corresponding bool valuetrue.

These parsing methods throw exceptions if their input is in an unrecognized format. For example, the statement bool.Parse("yes") throws a FormatException because that method understands only the values true and false.

When you use these methods to parse user input, you must be aware that they can throw exceptions if the user enters values with an invalid format. If the user enters ten in a TextBox where the program expects an int, the int.Parse method throws a FormatException. If the user enters 1E3 or100000 where the program expects a short, the short.Parse method throws an OverflowException.

You can use a try-catch block to protect the program from these exceptions, but to make value checking even easier, each of these classes also provides a TryParse method. This method attempts to parse a string and returns true if it succeeds or false if it fails. If it succeeds, the method also saves the parsed value in an output variable that you pass to the method.

Table 4-2 lists the most common data types that provide Parse and TryParse methods.

Table 4-2: Data Types That Provide Parse and TryParse Methods

bool

byte

char

DateTime

decimal

double

float

int

long

sbyte

short

TimeSpan

uint

ulong

ushort

The following code shows two ways a program can parse integer values that are entered in TextBoxes:

int quantity;

try

{

quantity = int.Parse(quantityTextBox.Text);

}

catch

{

quantity = 1;

}

int weight;

if (!int.TryParse(weightTextBox.Text, out weight)) weight = 10;

The code declares the variable quantity. Inside a try-catch block, the code uses int.Parse to try to convert the text in the quantityTextBox control into an integer. If the conversion fails, the code sets quantity to the default value 1.

Next, the code declares the variable weight. It then uses int.TryParse to attempt to parse the text in the weightTextBox control. If the attempt succeeds, the variable weight holds the parsed value the user entered. If the attempt fails, TryParse returns false, and the code sets weight to the default value 10.

BEST PRACTICES: Avoid Parsing When Possible

Sometimes, you can avoid parsing numeric values and dealing with invalid inputs such as ten by using a control to let the user select a value instead of entering one. For example, you could use a NumericUpDown control to let the user select the quantity instead of entering it in a TextBox.

Usually, the parsing methods work fairly well if their input makes sense. For example, the statement int.Parse("645") returns the value 645 with no confusion.

Even the DateTime data type’s Parse method can make sense out of most reasonable inputs. For example, in U.S. English the following statements all parse to 3:45 PM April 1, 2014.

DateTime.Parse("3:45 PM April 1, 2014").ToString()

DateTime.Parse("1 apr 2014 15:45").ToString()

DateTime.Parse("15:45 4/1/14").ToString()

DateTime.Parse("3:45pm 4.1.14").ToString()

By default, however, parsing methods do not handle currency values well. For example, the following code throws a FormatException (in the U.S. English locale):

decimal amount = decimal.Parse("$123,456.78");

The reason this code fails is that, by default, the decimal.Parse method enables thousands and decimal separators but not currency symbols.

You can make decimal.Parse enable currency symbols by adding another parameter that is a combination of values defined by the System.Globalization.NumberStyles enumeration. This enumeration enables you to indicate special characters that should be allowed such as the currency symbols, a leading sign, and parentheses.

Table 4-3 shows the values defined by the NumberStyles enumeration.

Table 4-3: NumberStyles Enumeration Values

Style

Description

None

Enables no special characters. The value must be a decimal integer.

AllowLeadingWhite

Enables leading whitespace.

AllowTrailingWhite

Enables trailing whitespace.

AllowLeadingSign

Enables a leading sign character. Valid characters are given by the NumberFormatInfo.PositiveSign and NumberFormatInfo.NegativeSign properties.

AllowTrailingSign

Enables a trailing sign character. Valid characters are given by the NumberFormatInfo.PositiveSign and NumberFormatInfo.NegativeSign properties.

AllowParentheses

Enables the value to be surrounded by parentheses to indicate a negative value.

AllowDecimalPoint

Enables the value to contain a decimal point. If AllowCurrencySymbol is also specified, the allowed currency symbol is given by the NumberFormatInfo.CurrencyDecimalSeparator property. If AllowCurrencySymbol is not specified, the allowed currency symbol is given by the NumberFormatInfo.NumberDecimalSeparator.

AllowThousands

Enables thousands separators. If AllowCurrencySymbol is also specified, the separator is given by the NumberFormatInfo.CurrencyGroupSeparator property and the number of digits per group is given by the NumberFormatInfo.CurrencyGroupSizes property. If AllowCurrencySymbol is not specified, the separator is given by the NumberFormatInfo.NumberGroupSeparatorproperty and the number of digits per group is given by the NumberFormatInfo.NumberGroupSizes property.

AllowExponent

Enables the exponent symbol e or E optionally followed by a positive or negative sign.

AllowCurrencySymbol

Enables a currency symbol. The allowed currency symbols are given by the NumberFormatInfo.CurrencySymbol property.

AllowHexSpecifier

Indicates that the value is in hexadecimal. This does not mean the input string can begin with a hexadecimal specifier such as 0x or &H. The value must include only hexadecimal digits.

Integer

This is a composite style that includes AllowLeadingWhite, AllowTrailingWhite, and AllowLeadingSign.

HexNumber

This is a composite style that includes AllowLeadingWhite, AllowTrailingWhite, and AllowHexSpecifier.

Number

This is a composite style that includes AllowLeadingWhite, AllowTrailingWhite, AllowLeadingSign, AllowTrailingSign, AllowDecimalPoint, and AllowThousands.

Float

This is a composite style that includes AllowLeadingWhite, AllowTrailingWhite, AllowLeadingSign, AllowDecimalPoint, and AllowExponent.

Currency

This is a composite style that includes all styles exceptAllowExponent and AllowHexSpecifier.

Any

This is a composite style that includes all styles exceptAllowHexSpecifier.

If you provide any NumberStyles values, any default values are removed. For example, by default decimal.Parse enables thousands and decimal separators. If you pass the value NumberStyles.AllowCurrencySymbol to the method, it no longer enables thousands and decimal separators. To allow all three, you need to pass the method all three values as in the following code:

decimal amount = decimal.Parse("$123,456.78",

NumberStyles.AllowCurrencySymbol |

NumberStyles.AllowThousands |

NumberStyles.AllowDecimalPoint);

Alternatively, you can pass the method the composite style Currency, as shown in the following code:

decimal amount = decimal.Parse("$123,456.78",

NumberStyles.AllowCurrencySymbol);

Locale-Aware Parsing

Parsing methods are locale-aware, so they try to interpret their inputs for the locale in which the program is running. You can see that in the descriptions in Table 4-3 that mention the NumberFormatInfo class. For example, the allowed currency symbol is defined by theNumberFormatInfo.CurrencySymbol property, and that property will have different values depending on the computer’s locale.

If the computer is localized for French as spoken in France, DateTime.Parse understands the French-style date “1 mars 2020,” but doesn’t understand the German version “1. März 2020.” (It understands the English version “March 1, 2020” in either the French or German locale.)

Similarly, if the computer’s locale is French, the int.Parse method can parse the text “123 456,78” but cannot parse the German-style value “123.456,78.”

All literal values within C# code should use U.S. English formats. For example, no matter what locale the computer uses, a C# program would use a double variable to “0.05” not “0,05” inside its code.

COMMON MISTAKES: Parsing Currency Values

Many beginning programmers don’t realize they can parse currency values. If a TextBox should hold currency values, parse it correctly so that the user isn’t told “$1.25” has an invalid numeric format.

System.Convert

The System.Convert class provides more than 300 methods (including overloaded versions) for converting one data type to another. For example, the ToInt32 method converts a value into a 32-bit integer (an int). Different overloaded versions of the methods take parameters of different types such as bools, bytes, DateTimes, doubles, strings, and so forth.

Table 4-4 lists the most useful data type conversion methods provided by the System.Convert class.

Table 4-4: Basic System.Convert Data Conversion Methods

ToBoolean

ToByte

ToChar

ToDateTime

ToDecimal

ToDouble

ToInt16

ToInt32

ToInt64

ToSByte

ToSingle

ToString

ToUInt16

ToUInt32

ToUInt64

Banker’s Rounding

The methods that convert to integral types (ToByte, ToIntXX, and ToUIntXX) use “banker’s rounding,” which means values are rounded to the nearest integer, but if there’s a tie, with the value ending in exactly .5, the value is rounded to the nearest even integer. For example, ToInt16(9.5)and ToInt16(10.5) both return 10.

All these methods throw an exception if their result is outside of the range of allowed values. For example, ToByte(255.5) returns 256, which is too big to fit in a byte, and ToUInt32(-3.3) returns 3, which is less than zero, so it won’t fit in an unsigned integer.

The Math.Round method uses banker’s rounding by default but also enables you to use parameters to indicate if it should round toward 0 instead. This method returns a result that is either a decimal or a double, so often code must use a cast to convert the result into an integral data type as in the following code:

float total = 100;

int numItems = 7;

int average = (int)Math.Round(total / numItems);

The System.Convert class also provides a ChangeType method that converts a value into a new type determined by a parameter at run time. For example, (int)Convert.ChangeType(5.5, typeof(int)) returns the integer 6. Often it is easier to use one of the more specific methods such as ToInt32instead of ChangeType.

System.BitConverter

The System.BitConverter class provides methods to convert values to and from arrays of bytes. The GetBytes method returns an array of bytes representing the value that you pass to it. For example, if you pass an int (which takes up 4 bytes of memory) into the method, it returns an array of 4 bytes representing the value.

The System.BitConverter class also provides methods such as ToInt32 and ToSingle to convert byte values stored in arrays back into specific data types.

For example, suppose an API function returns two 16-bit values packed into the left and right halves of a 32-bit integer. You could use the following code to unpack the two values:

int packedValue;

// The API function call sets packedValue here.

...

// Convert the packed value into an array of bytes.

byte[] valueBytes = BitConverter.GetBytes(packedValue);

'// Unpack the two values.

short value1, value2;

value1 = BitConverter.ToInt16(valueBytes, 0);

value2 = BitConverter.ToInt16(valueBytes, 2);

After the API function sets the value of packedValue, the code uses the BitConverter class’s GetBytes method to convert the value into an array of 4 bytes. The order of the bytes depends on whether the computer’s architecture is big-endian or little-endian. (You can use the BitConverter’sIsLittleEndian field to determine whether the value is big-endian or little-endian.)

The BitConverter class’s methods are quite specialized, so they are not described further here. For more information, see "BitConverter Class" at http://msdn.microsoft.com/library/3kftcaf9.aspx.

Boxing and Unboxing Value Types

Boxing is the process of converting a value type such as an int or bool into an object or an interface that is supported by the value’s type. Unboxing is the processing of converting a boxed value back into its original value.

For example, the following code creates an integer variable and then makes an object that refers to its value:

// Declare and initialize integer i.

int i = 10;

// Box i.

object iObject = i;

After this code executes, variable iObject is an object that refers to the value 10.

Boxing and unboxing take significantly more time than simply assigning one value type variable equal to another, so you should avoid boxing and unboxing whenever possible. If that’s true, why would you ever do it?

Usually boxing and unboxing occur automatically without taking any special action. Often this happens when you invoke a method that expects an object as a parameter but you pass it a value. For example, consider the following code:

int i = 1337;

Console.WriteLine(string.Format("i is: {0}", i));

The version of the string class’s Format method used here takes as parameters a format string and a sequence of objects that it should print. The method examines the objects and prints them appropriately.

The code passes the value variable i into the Format method. That method expects an object as a parameter, so the program automatically boxes the value.

Ideally, you could avoid this by making the Format method take an int as a parameter instead of an object, but then what would you do if you wanted to pass the method a double, DateTime, or Person object? Even if you made overloaded versions of the Format method to handle all the basic data types (int, double, string, DateTime, bool, and so on), you couldn’t handle all the possible combinations that might occur in a long list of parameters.

The solution is to make Format take nonspecific objects as parameters and then use reflection to figure out how to print them.

Similarly, you could use nonspecific objects for parameters to the methods that you write and then use reflection to figure out what to do with the objects. For more information on reflection, see Chapter 8, “Using Reflection.”

Usually, you’ll get better performance if you can use a more specific data type, interface, or generic type for parameters. For more information on generic types and methods, see Chapter 3.

Even if you’re willing to live with the performance hit, boxing and unboxing has a subtle side-effect that can lead to some confusing code. Consider again the following code:

// Declare and initialize integer i.

int i = 10;

// Box i.

object iObject = i;

After this code executes, variable iObject is an object that refers to the value 10, but it’s not the same value 10 stored in the variable i. That means if the code changes one of the values, the other does not also change. For example, take a look at the following code, which adds some statements to the previous version:

// Declare and initialize integer i.

int i = 10;

// Box i.

object iObject = i;

// Change the values.

i = 1;

iObject = 2;

// Display the values.

Console.WriteLine(i);

Console.WriteLine(iObject);

This code creates an integer variable i and boxes it with the variable iObject. It then sets i equal to 1 and iObject equal to 2. When the code executes the Console.WriteLine statements, the following results appear in the Output window:

1

2

The variable iObject seems to refer to the variable i but they are actually two separate values.

Incidentally, the Console.WriteLine method has many overloaded versions including one that takes an int as a parameter, so the first WriteLine statement in the previous code does not require boxing or unboxing. The second WriteLine statement must unbox iObject to get its current value 2.

The moral of the story is that you should avoid boxing and unboxing if possible by not storing references to value types in objects. If the program automatically boxes and unboxes a value as the string.Format method does, there’s usually not too much you can do about it. Finally, you should not declare method parameters or other variables to have the nonspecific type object unless you have no other choice.

Ensuring Interoperability with Unmanaged Code

Interoperability enables a C# program to use classes provided by unmanaged code that was not written under the control of the Common Language Runtime (CLR), the runtime environment that executes C# programs. ActiveX components and the Win32 API are examples of unmanaged code that you can invoke from a C# program.

The two most common techniques for allowing managed programs to use unmanaged code are COM Interop and Platform invoke (P/invoke). COM Interop is discussed briefly in the following section. This section deals with P/invoke.

To use P/invoke to access an unmanaged resource such as an API call, a program first includes a DllImport attribute to define the unmanaged methods that will be used by the managed program. The DllImport attribute is part of the System.Runtime.InteropServices namespace, so many programs add that namespace in a using statement to make using the attribute easier.

The DllImport attribute takes parameters that tell the managed program about an unmanaged method. The parameters indicate such things as the DLL that contains the method, the character set used by the method (Unicode or ANSI), and the entry point in the DLL used by the method. (If you omit this, the default is the name of the method.)

The program applies the attribute to a static extern method declaration. The declaration includes whatever parameters the method requires and defines the method’s return type. This declaration should be inside a class such as the class containing the code that uses the method.

For example, the following code fragment shows where the using statement and DllImport attribute are placed in the ShortPathNames example program (which is described shortly in greater detail). The DllImport statement is highlighted.

using System;

using System.Collections.Generic;

... Other standard "using" statements ...

using System.Runtime.InteropServices;

namespace ShortPathNames

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

[DllImport("kernel32.dll", CharSet = CharSet.Auto,

SetLastError = true)]

static extern uint GetShortPathName(string lpszLongPath,

char[] lpszShortPath, int cchBuffer);

... Application-specific code ...

}

}

The DllImport statement indicates that the method is in the kernel32.dll library, that the program should automatically determine whether it should use the Unicode or ANSI character set, and that the method should call SetLastError if there is a problem. If there is an error, the program can useGetLastWin32Error to see what went wrong.

The method’s declaration indicates that the program will use the GetShortPathName method, which converts a full path to a file into a short path that can be recognized by Windows. (If the method uses the Unicode character set, the method’s name usually ends with a “W” for “wide characters” as in GetShortPathNameW.) This method returns a uint and takes as parameters a string, char array, and int.

NOTE Often the prefixes on the parameter names give you hints about the purposes of those parameters. In this example, lpsz means “long pointer to string that’s zero-terminated” and cch means “count of characters.” If you read the online help for the GetShortPathName API function, you’ll find that those prefixes make sense.

The first parameter is the file path that you want to convert to a short path. When you call the method, P/Invoke automatically converts it into a null-terminated string. The second parameter should be a pre-allocated buffer where GetShortPathName can store its results. The third parameter gives the length of the buffer that you allocated, so GetShortPathName knows how much room it has to work with.

The method returns a uint indicating the length of the string that the method deposited in the lpszLongPath buffer.

You can figure out the syntax for this DllImport statement by staring at the method’s signature in the online help, in this case at http://msdn.microsoft.com/library/windows/desktop/aa364989.aspx. A much easier option, however, is to look up the method at http://www.pinvoke.net. This website contains DllImport statements for a huge number of Win32 API functions. It even sometimes includes examples, discussion, and links to the methods’ online documentation. When you need to use a Win32 API function, this is a great place to start.

Having declared the method, the program can now use it. The ShortPathNames example program, which is available for download on the book’s website, uses the method in the following code:

// Get the long file name.

string longName = fileTextBox.Text;

// Allocate a buffer to hold the result.

char[] buffer = new char[1024];

long length = GetShortPathName(

longName, buffer,

buffer.Length);

// Get the short name.

string shortName = new string(buffer);

shortNameTextBox.Text = shortName.Substring(0, (int)length);

This code gets a long file path entered by the user in the fileTextBox control and allocates a buffer of 1024 chars to hold the short path. It then calls the GetShortPathName method, passing it the long file path, the buffer, and the length of the buffer.

After the method returns, the program uses the buffer to initialize a new string. It uses the Substring method and the length returned by GetShortPathName to truncate the string to its proper length and displays the result.

Usually, the kind of DllImport statement shown earlier is good enough to get the job done. If you need more control over how values are converted between managed and unmanaged code, you can add the MarshalAs attribute to the method’s parameters or return value.

The following code shows a new version of the DllImport statement for the GetShortPathName method that uses MarshalAs attributes:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]

static extern uint GetShortPathName(

[MarshalAs(UnmanagedType.LPTStr)] string lpszLongPath,

[MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszShortPath,

uint cchBuffer);

The first MarshalAs attribute indicates that the first parameter is an LPTStr data type in the unmanaged code and should be treated as a string in the managed code.

The second MarshalAs attribute indicates that the second parameter is an LPTStr data type in the unmanaged code and should be treated as a StringBuilder in the managed code.

Of course, if you use this declaration, you need to change the code to use a StringBuilder for a buffer instead of an array of char.

Handling Dynamic Types

The DllImport and MarshalAs attributes described in the previous section enable you to tell the program where to find an unmanaged method, and what data types it uses for parameters and a return type. This enables the program to invoke unmanaged methods through P/invoke.

COM Interop provides another way a managed program can interact with unmanaged code. To use COM Interop, you need to give your program a reference to an appropriate library. To do that, look in the Solution Explorer, right-click the References entry, and select Add Reference. Find the reference that you want to add in the COM tab’s Type Libraries section (for example, Microsoft Excel 14.0 Object Library), check the box next to the entry, and click OK.

Adding the library reference tells your program (and Visual Studio) a lot about the unmanaged COM application. If you open the View menu and select Object Browser, you can use the Object Browser to search through the objects and types defined by the library. (For the Excel library mentioned earlier, look in the Microsoft.Office.Interop.Excel assembly.)

The library gives Visual Studio enough information for it to provide IntelliSense about some of the library’s members, but Visual Studio may still not understand all the types used by the library. C# 4.0 and later provide a special data type called dynamic that you can use in this situation. This is a static data type, but its true type isn’t evaluated until run time. At design and compile time, C# doesn’t evaluate the dynamic item’s type, so it doesn’t flag syntax errors for problems such as type mismatches because it hasn’t evaluated the dynamic type yet. This can be useful if you can’t provide complete information about an item’s type to the compiler.

C# considers objects defined by the unmanaged COM Interop code to have the dynamic type, so it doesn’t care at compile time what their actual types are. It skips checking the objects’ syntax and waits until run time to see if the code makes sense.

The ExcelInterop example program, which is available for download on the book’s website, uses the following code to make Microsoft Excel create a workbook:

// Open the Excel application.

Excel._Application excelApp = new Excel.Application();

// Add a workbook.

Excel.Workbook workbook = excelApp.Workbooks.Add();

Excel.Worksheet sheet = workbook.Worksheets[1];

// Display Excel.

excelApp.Visible = true;

// Display some column headers.

sheet.Cells[1, 1].Value = "Value";

sheet.Cells[1, 2].Value = "Value Squared";

// Display the first 10 squares.

for (int i = 1; i <= 10; i++)

{

sheet.Cells[i + 1, 1].Value = i;

sheet.Cells[i + 1, 2].Value = (i * i).ToString();

}

// Autofit the columns.

sheet.Columns[1].AutoFit();

sheet.Columns[2].AutoFit();

In this code the dynamic data type is used implicitly in a couple of places. Visual Studio doesn’t actually understand the data type of sheet.Cells[1, 1], so it defers type checking for that value. That lets the program refer to this entity’s Value property even though the program doesn’t know whether the cell has such a property. Actually, you could try to set sheet.Cells[1, 1].Whatever = i and Visual Studio won’t complain until run time when it tries to access the Whatever property and finds that it doesn’t exist.

Similarly, Visual Studio treats sheet.Columns[1] as having type dynamic, so it doesn’t know that the AutoFit method exists until run time.

For an example that’s more C#-specific, consider the following code, which is demonstrated by the CloneArray example program available for download on the book’s website:

// Make an array of numbers.

int[] array1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// This doesn't work because array1.Clone is an object.

//int[] array2 = array1.Clone();

// This works.

int[] array3 = (int[])array1.Clone();

array3[5] = 55;

// This also works.

dynamic array4 = array1.Clone();

array4[6] = 66;

array4[7] = "This won't work";

This code initializes an array of integers. The commented out code tries to use the array’s Clone method to make a copy of the array. Unfortunately, the Clone method returns a nonspecific object, so the code cannot save it in a variable that refers to an array of int. The next statement correctly casts the object into an int[] so that it works. The code then stores a new integer value in the array.

Next, the code declares array4 to have the type dynamic. The program clones the array and saves the clone in variable array4. At run time the program can tell that the clone is actually an int[10] so that is the data type it assigns to array4.

The final statement tries to save a string in array4[7]. At design and compile time, Visual Studio doesn’t try to validate this statement because array4 was declared dynamic. At run time, however, this fails because array4 is actually an int[] and cannot hold a string.

The dynamic data type enables you to avoid syntax errors when you do not know (or cannot know) the type of an object at compile time. Unfortunately, not understanding an object’s type at design time also means Visual Studio cannot provide type checking or IntelliSense. That means you need to ensure that the methods you invoke actually exist, that you can assign specific values to a dynamic variable or property, and that you don’t try to save a dynamic value in an incompatible variable. The program will complain about any mistakes at run time, but you won’t get much help at design and compile time.

To prevent these kinds of errors at run time, you should avoid the dynamic data type and use more specific data types whenever possible.

REAL-WORLD CASE SCENARIO: Order Entry Forms

Order entry forms similar to the one shown in Figure 4-3 are common in order processing applications. In this Real-World Case Scenario, you build a form similar to this one. You can make it a Windows Forms application, a Metro-style application, or even a Windows Phone application if you prefer.

Figure 4-3: This order entry form parses numeric and currency values entered by the user.

image

When the user clicks the OK button, validate the form and calculate and display the appropriate values. (Don’t worry about formatting output fields as currency. Just use the variables’ ToString methods to display the text. You learn how to format values as currency in the section “Formatting Values.”)

If all the values entered by the user are valid, display a message box telling the user that the order is okay and asking whether the program should continue. If the user clicks Yes on the message box, or if the user clicks the form’s Cancel button, close the form.

Make the following validations:

· If any of the fields in a row is nonblank, then all the fields in that row must be nonblank.

· Quantity is an integer between 1 and 100.

· Price Each is a decimal between $0.01 and $100,000.00. (Be sure to allow values with a currency format.)

· Tax rate is a decimal between 0.00 and 0.20. (Don’t worry about percentage values such as 7 percent now. You add that feature later after you learn about manipulating strings in the section “Manipulating Strings.”)

Hint: Don’t forget to add a using System.Globalization statement to make using NumberStyles easier.

As a follow-up question, can you improve the user interface to reduce the amount of data validation required?

Solution

Here is the solution:

1. There are several ways you can structure the program’s code to make it easier to use and maintain. This isn’t the focus of this chapter, however, so they aren’t covered in detail here. You can download the chapter’s code and look at the Ch04RealWorldScenario01 program for details. Briefly, however, you may want to consider writing the following methods:

a. DisplayErrorMessage displays a standard error message and sets the focus to a TextBox that has a missing or invalid value.

b. ValidateRequiredTextBox verifies that a particular TextBox has a nonblank value.

c. ValidateRow validates a row of input consisting of Description, Quantity, and Price Each TextBoxes.

2. To see if a TextBox has a blank value, compare its Text property to the empty string "" or to string.Empty.

3. To get a value from a TextBox, use the appropriate TryParse method. For example, the following code shows how the program might read a Price Each value:

// Try to parse priceEach.

if (!decimal.TryParse(priceEachTextBox.Text, NumberStyles.Currency,

null, out priceEach))

{

// Complain.

DisplayErrorMessage(

"Invalid format. Price Each must be an currency value.",

"Invalid Format", priceEachTextBox);

return true;

}

This code uses NumberStyles.Currency to enable currency values.

4. Use if statements to determine whether values fall within their expected bounds. The following code shows how the program might validate a Price Each value:

// Make sure priceEach is between $0.01 and $100,000.00.

if ((priceEach < 0.01m) || (priceEach > 100000.00m))

{

// Complain.

DisplayErrorMessage(

"Invalid Price Each. Price Each must be between $0.01 and $100,000.00",

"Invalid Quantity", priceEachTextBox);

return true;

}

5. Calculate and display the Extended Price, Subtotal, Sales Tax, and Grand Total values. The following code shows how the example solution processes the order form’s first row:

subtotal = 0;

if (ValidateRow(descr1TextBox, quantity1TextBox, priceEach1TextBox,

out quantity, out priceEach)) return;

extendedPrice = quantity * priceEach;

if (extendedPrice == 0m) extendedPrice1TextBox.Clear();

else extendedPrice1TextBox.Text = extendedPrice.ToString();

subtotal += extendedPrice;

This code calls the ValidateRow method to validate and get the first row’s Description, Quantity, and Price Each values. If that method indicates an error by returning true, the code returns. If the row does not contain an error, the code calculates extendedPrice and displays its value in the appropriate TextBox. It then adds the row’s extended price to the running subtotal value and continues to process the other rows.

Download the example solution to see the complete code.

Follow-up question: One obvious way to improve the user interface would be to remove the Quantity TextBoxes and replace them with NumericUpDown controls. Then the user can select a value within minimum and maximum allowed values. The user couldn’t type in garbage and couldn’t select values outside of the allowed range.

You can even use a NumericUpDown control for the Tax Rate by setting its properties Minimum = 0, Maximum = 0.2, Increment = 0.05, and DecimalPlaces = 2.

You could also use NumericUpDown controls for the Price Each fields but that control makes entering monetary values awkward.

In general it’s better to let users select a value instead of entering one in a TextBox so that they can’t enter invalid values.

Manipulating Strings

Strings are different from other data types. Programs usually treat them as if they were any other value-type piece of data but behind the scenes the string class is remarkably complex. You can ignore the extra complexity in most day-to-day programming, but it is important to understand howstrings work so that you can handle special situations when they arise. For example, if you understand how strings are stored, you will know when it would be better to use the StringBuilder class instead of simply concatenating strings together.

NOTE In C# the keyword string is an alias for System.String, so when you create a string variable, you are actually creating a String object. Stylistically most C# programmers prefer to use string, but the following sections use String to emphasize that these are objects and not the simple value types they may appear to be.

Behind the Strings

The .NET Framework represents characters as Unicode version UTF-16, a format that uses 16 bits to store each character. That enables a Unicode character to represent far more characters than are provided on a standard American keyboard. (The latest version of Unicode defines values for more than 110,000 characters in more than 100 scripts.)

A String is an object that uses a series of Unicode characters to represent some text. One of the more unusual features of Strings is that they are immutable. That means a String’s content cannot be changed after the String has been created. Instead, methods that seem to modify a String’s value, such as Replace and ToUpper, actually return a new String object that contains the modified value.

To conserve memory, the CLR maintains a table called the intern pool that holds a single reference to each unique text value used by a program. Any String variable that refers to a particular piece of text is actually a reference into the intern pool. Multiple Strings that represent the same value refer to the same entry in the intern pool.

All this requires some overhead, so working with Strings is not quite as fast as working with value types. If a program must perform a large number of concatenations, each one creates a new String instance that must be interned and that takes time. In that case, using the StringBuilder class might give better performance. The StringBuilder class is described further in the section “StringBuilder.”

String Constructors

Three of the most common ways to initialize a String variable are to:

· Set it equal to a string literal.

· Set it equal to text entered by the user in a control such as a TextBox or ComboBox.

· Set it equal to the result of a string calculation.

The last of these includes methods that format a variable to produce a String such as using the ToString method or the String.Format method. These techniques are described in the section “Formatting Values.”

In addition to these methods, the String class provides several constructors that can sometimes be useful:

· One constructor initializes the String from an array of char.

· A second constructor uses only part of an array of char, taking as parameters the array, a start position, and the length of characters to use.

· A third constructor takes as a parameter a character and the number of times you want to repeat that character in the new String. This can be particularly useful if you want to indent a string by a certain number of spaces or tab characters. For example, the following code displays the numbers 1 through 10 on separate lines with each line indented four more spaces than the one before:

· for (int i = 1; i <= 10; i++)

· {

· string indent = new string(' ', 4 * i);

· Console.WriteLine(indent + i.ToString());

}

Most String values are created by string literals, text entered by the user, or the results of calculations, but String constructors can sometimes be useful.

String Fields and Properties

The String class provides only three fields and properties: Empty, Length, and a read-only indexer.

The Empty field returns an object that represents an empty string. You can use this value to set a String’s value or to see if a String holds an empty value. (Alternatively, you can use the empty string literal "".)

The Length property returns the number of characters in the string.

The read-only indexer returns the chars in the String. Because it is an indexer, you can get its values by adding an index to a String variable’s name. For example, the statement username[4] returns character number 4 in the string username.

The indexer is read-only, so you can’t set one of the String’s characters with a statement such as username[4] = 'x'. If you need to do something like that, you can use the String methods described in the next section.

If it would be easier to treat the String as if it were a read/write array of characters, you can use the ToCharArray method to convert the String into an array of characters, manipulate them, and then create a new String passing the constructor the modified array. For example, the following code uses an array to make a string’s characters alternate between uppercase and lowercase:

char[] characters = text.ToCharArray();

for (int i = 0; i < characters.Length; i++)

if (i % 2 == 0) characters[i] = char.ToUpper(characters[i]);

else characters[i] = char.ToLower(characters[i]);

text = new string(characters);

You can also use the indexer as a source of iteration in a foreach loop:

string text = "The quick brown fox jumps over the lazy dog.";

int[] counts = new int[26];

text = text.ToUpper();

foreach (char ch in text)

{

if (char.IsLetter(ch))

{

int index = (int)ch - (int)'A';

counts[index]++;

}

}

This code makes a String object named text. It creates a counts array to hold counts for the 26 letters A to Z used in the string. Before processing the string, the code then converts text into uppercase.

Next, the code uses a foreach statement to loop over the characters in the string. For each character, the code uses the char class’s IsLetter method to decide whether the character is a letter and not a space or punctuation mark. If the character is a letter, the code converts it into an integer and subtracts the value of “A” converted into an integer from it to get an index into the counts array. The letter A has index 0, B has index 1, and so forth. The code then increments the count for that index. When the code finishes, the counts array holds the number of times each character occurs in the string.

String Methods

The String class provides lots of methods that enable you work with strings. Table 4-5 describes the most useful static methods provided by the String class. Because these are static methods, a program uses the String class to invoke these methods. For example, to use the Compare method, the program uses a statement similar to if (String.Compare(value1, value2) > 0) ... .

Table 4-5: Useful Static String Methods

Method

Description

Compare

Compares two Strings and returns -1, 0, or 1 to indicate that the first String should be considered before, equal to, or after the second String in the sort order. Overloaded versions of this method enable you to specify string comparison rules, whether to ignore case, and which culture’s comparison rules to use.

Concat

Takes as a parameter an array of Strings or other objects and returns a String holding the concatenation of the objects. An overloaded version enables you to pass any number of arguments as parameters and returns the arguments concatenated. See also Join.

Copy

Returns a copy of the String. See also the Clone instance method in Table 4-6.

Equals

Returns true if two Strings have the same value. See also the Equals instance method in Table 4-6.

Format

Uses a format string and a series of objects to generate a formatted text string. See the “String.Format” section for more information.

IsNullOrEmpty

Returns true if the String holds a blank string "" or the String variable refers to null. The following code sets two variables equal to an empty string and a third variable to null:

string value1 = "";

string value2 = String.Empty;

string value3 = null;

There is a difference between an empty string and null. The IsNullOrEmpty method makes it easier to treat both values in the same way.

IsNullOrWhiteSpace

Returns true if the String variable holds a blank string, refers to null, or holds only whitespace characters. Whitespace characters are those for which Char.IsWhiteSpace returns true.

Join

Joins the values in an array of strings or other objects separated by a separator string. For example, the following code sets the variable allDays to hold the days of the week separated by commas:

string[] weekdays =

{

"Monday", "Tuesday", "Wednesday",

"Thursday", "Friday", "Saturday",

"Sunday"

};

string allDays = string.Join(",", weekdays);

Table 4-6 describes the most useful instance methods provided by the String class. Because these are instance methods, a program must use an instance of the String class to invoke these methods. For example, to use the CompareTo method, the program would use a statement similar to if (value1.CompareTo(value2) > 0) ... .

Table 4-6: Useful String Instance Methods

Method

Description

Clone

Returns a new reference to the String. The behavior is a bit different from the Clone methods provided by most other classes because Strings are immutable. For this class, the new reference refers to the same value in the intern pool as the original String. Refer also to the static Copy method in Table 4-5.

CompareTo

Compares the String to another String and returns -1, 0, or 1 to indicate that this String should be considered before, equal to, or after the other String in the sort order. If you want to specify string comparison rules, whether to ignore case, and which culture’s comparison rules to use, use the static Compare method.

Contains

Returns true if the String contains a specified substring.

CopyTo

Copies a specified number of characters from a specified start position into a char array.

EndsWith

Returns true if the String ends with a specified substring. Overloaded versions enable you to specify string comparison type, whether to ignore case, and culture.

Equals

Returns true if this String has the same value as another String. Refer also to the static Equals method in Table 4-5.

IndexOf

Returns the index of the first occurrence of a character or substring within the String. Parameters enable you to specify the position in the String where the search should begin and end, and string comparison options.

IndexOfAny

Returns the index of the first occurrence of any character in an array within the String. Parameters enable you to specify the position in the String where the search should begin and end.

Insert

Inserts a String at a specific position within this String and returns the result.

LastIndexOf

Returns the index of the last occurrence of a character or substring within the String. Parameters enable you to specify the position in the String where the search should begin and end, and comparison options.

LastIndexOfAny

Returns the index of the last occurrence of any character in an array within the String. Parameters enable you to specify the position in the String where the search should begin and end.

PadLeft

Returns the String padded to a certain length by adding spaces or a specified character on the left. This makes it easier to align text in columns with a fixed-width font.

PadRight

Returns the String padded to a certain length by adding spaces or a specified character on the right. This makes it easier to align text in columns with a fixed-width font.

Remove

Removes the characters starting at a specified position either to the end of the String or for a certain number of characters and returns the result.

Replace

Replaces all instances of a character or string with another character or string and returns the result.

Split

Returns an array holding the String’s pieces as delimited by characters in an array. Overloaded versions enable you to indicate the maximum number of pieces to return and split options such as whether to remove empty entries. For example, the following code splits a series of numbers separated by commas and dashes, removing any entries that are empty:

char[] delimiters = { ',', '-' };

string values = "12-21,,33-17,929";

string[] fields = values.Split(delimiters,

StringSplitOptions.RemoveEmptyEntries);

StartsWith

Returns true if the String starts with a specified substring. Overloaded versions enable you to specify comparison type, whether to ignore case, and culture.

Substring

Returns a new String containing a substring of this String specified by a start position and length.

ToCharArray

Returns an array of char representing some or all the String’s characters.

ToLower

Returns a copy of the String converted to lowercase.

ToString

Returns the String. Normally, you don’t need to do this, but if you’re treating the String as an object, for example if it is in a list or array of objects, it’s useful to know that this object has a ToString method.

ToUpper

Returns a copy of the String converted to uppercase.

Trim

Returns a copy of the String with leading and trailing whitespace characters removed. An overloaded version enables you to specify which characters should be removed.

TrimEnd

Returns a copy of the String with trailing whitespace characters removed.

TrimStart

Returns a copy of the String with leading whitespace characters removed.

The String class’s methods let a program perform all sorts of string manipulations such as parsing user input to get the pieces of an address, phone number, or other pieces of formatted information. Chapter 11, "Input Validation, Debugging and Instrumentation," has more about parsing and validating user input by using the String class’s methods.

REAL-WORLD CASE SCENARIO: Handling Percentage Values

Modify the order entry form that you built for this chapter’s first Real-World Case Scenario so that it can handle Tax Rate specified as a percentage. If the value entered by the user contains a % character, parse the value and divide it by 100.

Solution

The decimal.TryParse method cannot parse a string that contains the % character. To parse the value, the program must remove the % character if it is present, use TryParse to convert the result into a decimal value, and then divide by 100 if the original text contained the % character.

The following code snippet shows one way the program can do this:

// Get the tax rate as a string.

string taxRateString = taxRateTextBox.Text;

// Remove the % character if it is present.

taxRateString = taxRateString.Replace("%", "");

// Parse the tax rate.

decimal taxRate;

if (!decimal.TryParse(taxRateString, out taxRate))

{

// Complain.

DisplayErrorMessage(

"Invalid format. Tax Rate must be a decimal value.",

"Invalid Format", taxRateTextBox);

return;

}

// If the original string contains the % character, divide by 100.

if (taxRateTextBox.Text.Contains("%")) taxRate /= 100;

Additional String Classes

Figure 4-4: The permutations example program displays the permutations of a set of letters.

image

The String class is intuitive and easy to use. You can use its methods to easily examine Strings, remove sections from Strings, trim a String’s start or end, and extract substrings.

The unusual way Strings are interned, however, makes them inefficient for some purposes. Figure 4-4 shows the permutations example program, which is available for download on the book’s website. This program displays a big String holding all the permutations of a set of letters. In Figure 4-4, the program is showing permutations of the letters A through H.

There are 8! (or 5040 permutations of those eight letters) so the result is 5040 Strings concatenated together. To make matters worse, the program builds each permutation one character at a time, so each permutation requires building eight smaller Strings. That means the program builds 8 × 5040 = 40,320 Strings in all, each of which must be interned. As a result, the program is quite slow, taking approximately 23 seconds to produce these 5,040 permutations by using String concatenation.

For special cases such as this, when the String class is particularly inefficient, a program may get better performance by using the specialized string processing classes:

· StringBuilder

· StringWriter

· StringReader

Referring to Figure 4-4, you can see that the program took only 0.05 seconds to build the permutations when it used a StringBuilder instead of String concatenations. Each of these string processing classes is described in the following sections.

StringBuilder

The StringBuilder class represents a mutable, noninterned string. It stores character data in an array and can add, remove, replace, and append characters without creating a new String object or using the intern pool.

Normally, a program uses a StringBuilder to build a string in a long series of steps and then calls the StringBuilder’s ToString method to convert the result into a normal String.

For example, the following code uses a StringBuilder to build a string holding a series of employee names on separate lines:

string[] employeeNames =

{

"Able",

"Baker",

"Charley",

"Davis",

};

StringBuilder allNames = new StringBuilder();

foreach (string name in employeeNames)

{

allNames.Append("[" + name + "]" + Environment.NewLine);

}

employeeTextBox.Text = allNames.ToString();

The code starts by defining an array of employee names. It then creates a StringBuilder and loops over the names in the EmployeeNames array. For each name the code calls the StringBuilder’s Append method to add the name surrounded by brackets to the string. After it has processed all the names, the code calls the StringBuilder’s ToString method to convert it into a normal String and displays the result in the employeeTextBox control.

Table 4-7 describes the StringBuilder class’s most useful properties.

Table 4-7: Useful StringBuilder Properties

Property

Description

Capacity

Gets or sets the number of characters that can be held by the StringBuilder. If the amount of text stored in the StringBuilder exceeds this amount, the object allocates more space. If you know the StringBuilder needs to hold at least a certain number of characters, you can use this property to make the object pre-allocate memory instead of allocating memory incrementally. Some overloaded versions of the class’s constructor let you specify an initial capacity.

Length

Gets or sets the current number of the characters stored in the StringBuilder. If you set this value to less than the current length, the text in the StringBuilder is truncated.

The StringBuilder’s indexer returns the characters stored in the object. A program can use the indexer to get and set character values. For example, the statement allNames[10] = 'X' sets character number 10 to X.

Table 4-8 describes the StringBuilder class’s most useful methods.

Table 4-8: Useful StringBuilder Methods

Method

Description

Append

Appends a string representation of an object to the end of the StringBuilder’s text

AppendFormat

Formats a series of objects and appends the result to the end of the StringBuilder’s text

EnsureCapacity

Ensures that the StringBuilder has at least a given capacity

Insert

Inserts a string representation of an object at a given position in the StringBuilder’s text

Remove

Removes a range of characters from the StringBuilder’s text

Replace

Replaces all instances of a character or string with a new character or string

ToString

Returns a normal String representation of the StringBuilder’s text

The StringBuilder class does add some overhead to a program and sometimes makes the code harder to read, so you should generally use it only if you perform a large number of string operations. In one set of tests, simple String concatenation was faster than creating and using aStringBuilder for fewer than approximately seven concatenations.

Also keep in mind that the times involved for a few String operations are small. Using a StringBuilder to concatenate 10 strings may be slightly faster than performing 10 simple String concatenations, but the total amount of time saved is measured in milliseconds. Unless the program repeats that operation many times or makes much longer concatenations, it may be better to sacrifice a few milliseconds to keep the code easier to understand.

StringWriter

The StringWriter class provides an interface that makes it easier in some cases to build a string on an underlying StringBuilder. The StringWriter class provides methods that make it easier to sequentially write values into a string.

Table 4-9 describes the StringWriter’s most useful methods.

Table 4-9: Useful StringWriter Methods

Method

Description

Flush

Flushes any buffered data into the underlying StringWriter.

ToString

Returns the object’s current contents as a String.

Write

Appends an item to the string data. Overloaded versions append char, string, int, double, and many other data types.

WriteAsync

Asynchronously appends a char, string, or array of char to the end of the string data.

WriteLine

Appends an item to the string data much as Write does and then adds a new line.

StringWriter can be useful when you want to append values only to a string. StringWriter also implements a TextWriter interface, so it can be useful when other classes require a TextWriter to produce output and you want to store that output in a string. For example, the XmlSerializer class’sSerialize method sends output to a TextWriter. If you want to serialize into a string, you can send the output to a StringWriter and then use the StringWriter’s ToString method to get the result. If you need to manipulate the underlying string data in other ways, such as removing or replacing characters, StringBuilder provides more flexibility.

StringReader

The StringReader class provides a TextReader implementation that reads pieces of data taken from an underlying StringBuilder. It provides methods that make it easier to sequentially read pieces of text from a string.

Table 4-10 describes the StringReader’s most useful methods.

Table 4-10: Useful StringReader Methods

Method

Description

Peek

Returns the next character in the data but does not advance to the following character.

Read

Returns the next character in the data and advances to the following character. An overloaded version can read a block of characters.

ReadAsync

Asynchronously reads characters from the StringReader into a buffer.

ReadBlock

Reads up to a maximum number of characters from the StringReader into a buffer beginning at a specified index.

ReadBlockAsync

Asynchronously reads up to a maximum number of characters from the StringReader into a buffer beginning at a specified index.

ReadLine

Reads characters from the StringReader until it encounters the end of the line.

ReadLineAsync

Asynchronously reads characters from the StringReader until it encounters the end of the line.

ReadToEnd

Returns the remaining text from the StringReader as a String.

ReadToEndAsync

Asynchronously returns the remaining text from the StringReader as a String.

The StringReader class provides access to a StringBuilder’s data at a relatively low level. Often a program uses a StringReader only because it needs to pass information to a predefined method that requires a StringReader or TextReader as a parameter.

REAL-WORLD CASE SCENARIO: Using StringBuilder

Using only StringBuilders (no Strings), write a program that displays all the initial subsequences of the letters of the alphabet A, AB, ABC, and so forth in a TextBox, as shown in Figure 4-5.

Solution

The following code does the job:

private void Form1_Load(object sender, EventArgs e)

{

// Make a StringBuilder holding the ABCs.

StringBuilder letters =

new StringBuilder("ABCDEFGHIJKLMNOPQRSTUVWXYZ");

// This one holds the next line of letters.

StringBuilder line = new StringBuilder();

// Create the result StringBuilder.

StringBuilder result = new StringBuilder();

// Loop over the letters.

for (int i = 0; i < 26; i++)

{

// Add the next letter to line.

line.Append(letters[i]);

// Add line to the result.

result.AppendLine(line.ToString());

}

// Display the result.

stringBuilderTextBox.Text = result.ToString();

stringBuilderTextBox.Select(0, 0);

}

Figure 4-5: This program uses StringBuilders to list initial sequences of the alphabet.

image

The code first builds a StringBuilder holding the letters of the alphabet. It makes a second StringBuilder to hold a line of output and a third to hold the final result.

Next, the code loops over the numbers 0 through 25. For each value of i, the code appends the ith character in letters to the value in line. It then appends the new value of line to the result, following it with a new line.

When the code finishes its loop, it displays the result.

In this example it’s not clear whether using StringBuilder is faster than using simple String concatenations. In one test that executed this code and similar code that performed String concatenations 100,000 times, the StringBuilder version took approximately 54 percent as long, so there is a time-savings, but the result for a single execution is negligible.

Formatting Values

Formatting a value for display is a particularly important type conversion. Until you convert a DateTime, decimal, or double into some sort of String, you can’t display it to the user.

Two of the most useful methods for formatting values as strings are the ToString and String.Format methods described in the next two sections. Both of those methods use formatting strings, which are described in the section after those.

ToString

The object class provides a ToString method that every other class inherits. By default this method returns an object’s type name as a String, but most classes for which it makes sense override this method to return the object’s value as a String.

For example, if a float variable holds the value 1.23, its ToString method returns the value “1.23” as a string. In contrast, if you define an Employee class, by default its ToString method returns the name of the class, which is similar to WindowsFormsApplication1.Employee.

If you use a variable’s ToString method without parameters, you get a default representation of its value.

The ToString method can also take as parameters a format provider, a formatting string, or both. By using the formatting string, you can customize the resulting text. For example, if the variable cost is a float, the statement cost.ToString("0.00") produces a string holding the value of costdisplayed to 2 decimal places.

String.Format

The ToString method enables you to convert a single variable’s value into a String. The String class’s static Format method enables you to build a String that may contain the values of many variables formatted in different ways.

The String.Format method has a few overloaded versions, but the most common takes as parameters a formatting string and one or more arguments that are used to fill in items within the formatting string.

Each format item in the formatting string has the following composite format syntax:

{index[,length][:formatString]}

Here, index is the zero-based index of a parameter that follows the formatting string that should be used for this item; length is the minimum length of the result for the item; and formatString is a standard or custom format string for the item. If length is negative, the value is left-aligned within its length.

Stating this formally makes it sound confusing but it’s actually not too bad. The following code shows a simple example.

int i = 163;

Console.WriteLine(string.Format("{0} = {1,4} or 0x{2:X}", (char)i, i, i));

The code defines an int variable named i and sets it equal to 163. It then uses string.Format to format a line that it writes to the Output window.

The format string is {0} = {1,4} or 0x{2:X}. This string has three format items that mean:

· {0} displays argument 0 with default formatting

· {1,4} displays argument 1 in a field at least four characters wide

· {2:X} displays argument 2 with format string X (which displays an integer in hexadecimal)

The other characters inside the formatting string (=, and or 0x) are included in the output as they appear in the formatting string.

The parameters that come after the formatting string are the arguments that should be used with the formatting string. The first argument casts the integer i into a char. The second and third arguments are simply the variable i.

The result is that this line displays the value 163 converted into a character, then as a decimal value, and then in hexadecimal. The following shows the result:

£ = 163 or 0xA3

An argument does not need to be used in the formatting string. Arguments can also be used in any order and may be used repeatedly, so the following statement is valid:

string text = string.Format("{1} {4} {2} {1} {3}",

"who", "I", "therefore", "am", "think");

Whether you use String.Format or concatenate a series of statements together to produce output is largely a matter of personal preference.

Formatting Strings

Both the ToString and String.Format methods can take formatting strings as parameters to tell them how to format a value. For String.Format this refers to the formatting string within format items. For example, in the statement string.Format("0x{0:X}", 90), the formatting string is the X inside the braces.

Formatting strings fall into two broad categories:

· Standard formatting strings enable you to determine how you want a value displayed at a high level. The standard formatting strings are locale-aware, so they let the program produce an output that is appropriate for the computer’s locale. For example, the “d” date format string indicates a short date pattern and produces a result similar to 3/14/2014 in the United States or 14/03/2014 in France.

· Custom formatting strings enable you to build formats that are not provided by the standard formatting strings. For example, the following statement produces a result similar to It is now 14 o’clock.

Console.WriteLine(string.Format("It is now {0:HH} o'clock", DateTime.Now));

You can use custom formatting strings to produce results that are similar to those produced by the standard strings, but you should use the standard strings whenever possible so that you get appropriate changes if your program runs on a computer that is configured for a different locale.

The ToString and String.Format methods understand hundreds of standard and custom formatting strings. Some are so seldom used that listing them all here would waste a lot of space. The following two tables list the most useful standard formatting strings for numeric and DateTime values. For complete lists of the allowed standard and custom formatting strings, see the URLs in the “Additional Reading and Resources” section.

Tables 4-11 describes the most useful standard numeric formatting strings.

Table 4-11: Standard Numeric Format Strings

Format

Description

Example

C or c

Currency

$12,345.67

D or d

Decimal (integer types only)

12345

E or e

Scientific notation

1.234567E+004

F or f

Fixed-point

12345.67

G or g

General (fixed-point or scientific, whichever is shorter)

12345.67

N or n

Number (with decimal and thousands separators)

12,345.67

P or p

Percent (multiplied by 100 and % added)

0.12 becomes 12.00 %

X or x

Hexadecimal (integer types only)

3039

Some of these formats can take an optional precision specifier that controls the number of digits displayed. For most of these types, the precision specifier indicates the number of digits to display after the decimal point. For example, if value is 12345.67 then value.ToString("C4") produces$12,345.6700.

For scientific notation the precision specifier indicates the number of digits after the decimal point in the mantissa. For example, if value is 12345.67, then value.ToString("E2") produces 1.23E+004.

Tables 4-12 describes the most useful standard DateTime formatting strings.

Table 4-12: Standard DateTime Format Strings

Format

Description

Example

d

Short date

3/14/2014

D

Long date

Friday, March 14, 2012

f

“Full” with short time

Friday, March 14, 2012 2:15 PM

F

“Full” with long time

Friday, March 14, 2012 2:15:16 PM

g

“General” with short time

3/14/2014 2:15 PM

G

“General” with long time

3/14/2014 2:15:16 PM

m or M

Month/day

March 14

t

Short time

2:15 PM

T

Long time

2:15:16 PM

y or Y

Year/month

March, 2014

In addition to these standard formats, the DateTime structure provides four methods that produce output similar to the d, D, t, and T format specifiers. These methods are ToShortDateString, ToLongDateString, ToShortTimeString, and ToLongTimeString.

For more information on these and other formatting strings, see the URLs in the “Additional Reading and Resources” section.

REAL-WORLD CASE SCENARIO: Displaying Currency Values

Modify the order entry form that you built for this chapter’s second Real-World Case Scenario (Handling Percentage Values) so it displays Extended Price, Subtotal, Sales Tax, and Grand Total in currency format.

Solution

The program already uses the ToString method to display those values. The only change needed is to pass the currency formatting string “C” to those calls to ToString. For example, the following code shows how the program displays the Grand Total in currency format:

grandTotalTextBox.Text = grandTotal.ToString("C");

Summary

This chapter explained how to work with types. It explained how to convert from one type to another using both implicit and explicit conversions. It explained how to use classes such as System.Convert and System.BitConverter to perform more specialized data type conversions.

This chapter also explained how to use the String class to manipulate strings, and how to use the String.Format and ToString methods to convert values into text for display.

One of the many kinds of type conversion a program can make is between classes. For example, if the Employee class is derived from the Person class, then you can implicitly convert an Employee into a Person and, in some cases you can explicitly cast a Person into an Employee. The next chapter explains how you can build the class hierarchies that enable these sorts of conversions.

Test Questions

Read each question carefully and select the answer or answers that represent the best solution to the problem. You can find the answers in Appendix A, “Answers to Chapter Test Questions.”

1. To parse a string that might contain a currency value such as $1,234.56, you should pass the Parse or TryParse method which of the following values?

a. NumberStyles.AllowCurrencySymbol

b. NumberStyles.AllowThousands

c. NumberStyles.Currency

d. A combination of all NumberStyles values

2. Which of the following statements is true for widening conversions?

a. Any value in the source type can fit into the destination type.

b. The conversion will not result in loss of magnitude but may result is some loss of precision.

c. An explicit cast is optional.

d. All of the above.

3. Which of the following statements is true for narrowing conversions?

a. The conversion will not result in loss of magnitude but may result is some loss of precision.

b. The source and destination types must be compatible.

c. An explicit cast is optional.

d. A cast can convert a string into an int if the string holds numeric text.

4. Assuming total is a decimal variable holding the value 1234.56, which of the following statements displays total with the currency format $1,234.56?

a. Console.WriteLine(total.ToString());

b. Console.WriteLine(total.ToCurrencyString());

c. Console.WriteLine(total.ToString("c"));

d. Console.WriteLine(Format("{0:C}", total);

5. Which of the following statements generates a string containing the text "Veni, vidi, vici"?

a. String.Format("{0}, {1}, {2}", Veni, vidi, vici)

b. String.Format("{1}, {2}, {3}", "Veni", "vidi", "vici")

c. String.Format("{2}, {0}, {3}", "vidi", "Venti", "Veni", "vici")

d. String.Format("{Veni, vidi, vici}")

6. If i is an int and l is a long, which of the following statements is true?

a. i = (int)l is a narrowing conversion.

b. l = (long)i is a narrowing conversion.

c. l = (long)i could cause an integer overflow.

d. The correct way to copy i’s value into l is l = long.Parse(i).

7. Which of the following methods is the best way to store an integer value typed by the user in a variable?

a. ToString

b. Convert

c. ParseInt

d. TryParse

8. The statement object obj = 72 is an example of which of the following?

a. Explicit conversion

b. Immutable conversion

c. Boxing

d. Unboxing

9. If Employee inherits from Person and Manager inherits from Employee, which of the following statements is valid?

a. Person alice = new Employee();

b. Employee bob = new Person();

c. Manager cindy = new Employee();

d. Manager dan = (Manager)(new Employee());

10. Which of the following is not a String method?

a. IndexOf

b. StartsWith

c. StopsWith

d. Trim

11. Which of the following techniques does not create a String containing 10 spaces?

a. Set a String variable equal to a literal containing 10 spaces.

b. Use a String constructor passing it an array of 10 space characters.

c. Use a String constructor passing it the space character and 10 as the number of times it should be repeated.

d. Use the String class’s Space method passing it 10 as the number of spaces the string should contain.

12. Which of the following statements can you use to catch integer overflow and underflow errors?

a. checked

b. overflow

c. watch

d. try

13. Which of the following techniques should you use to watch for floating point operations that cause overflow or underflow?

a. Use a checked block.

b. Use a try-catch block.

c. Check the result for the value Infinity or NegativeInfinity.

d. Check the result for Error.

Additional Reading and Resources

Following are some additional useful resources to help you understand the topics presented in this chapter:

Explanation of Big Endian and Little Endian Architecture http://support.microsoft.com/kb/102025

Convert Class http://msdn.microsoft.com/library/system.convert.aspx

BitConverter Class http://msdn.microsoft.com/library/3kftcaf9.aspx.)

Standard Date and Time Format Strings http://msdn.microsoft.com/library/az4se3k1.aspx

Custom Date and Time Format Strings http://msdn.microsoft.com/library/8kb3ddd4.aspx

Standard Numeric Format Strings http://msdn.microsoft.com/library/dwhawy9k.aspx

Custom Numeric Format Strings http://msdn.microsoft.com/library/0c899ak8.aspx

Standard TimeSpan Format Strings http://msdn.microsoft.com/library/ee372286.aspx

Custom TimeSpan Format Strings http://msdn.microsoft.com/library/ee372287.aspx

Enumeration Format Strings http://msdn.microsoft.com/library/c3s1ez6e.aspx

Understanding the Dynamic Keyword in C# 4 http://msdn.microsoft.com/en-us/magazine/gg598922.aspx

Cheat Sheet

This cheat sheet is designed as a way for you to quickly study the key points of this chapter.

Conversion Basics

· Implicit conversion doesn’t use a cast operator.

· Explicit conversion uses a cast operator.

· Widening conversions always succeed and a cast is optional. Magnitude is never lost but precision may be.

· Narrowing conversions do not always succeed, and a cast or other conversion method is required.

· Integer operations (including casting) that result in overflow or underflow are ignored unless you use a checked block or the Advanced Builds Settings dialog.

· Floating point operations that result in overflow or underflow are ignored. Check the result for the value Infinity or NegativeInfinity to see if overflow or underflow has occurred.

· You can cast arrays of references but be aware that the new array refers to the same array and not a new one.

The is and as Operators

· Use the is operator to determine if a variable is compatible with a certain type.

· Use the as operator to convert an object into a compatible type (or null if the object isn’t compatible with the type).

· The as operator is particularly useful if you know an object’s type, for example in an event handler.

Parsing

· Use the Parse method to parse text into a value. You must protect Parse method calls with try-catch blocks.

· Use the TryParse method to attempt to parse text and see if there is an error. TryParse returns true if it succeeds and false if there is an error.

· Use the System.Globalization.NumberStyles enumeration to allow Parse and TryParse to understand special symbols such as thousands separators, decimal points, and currency symbols.

· Some useful NumberStyles values include Integer, HexNumber, Number, Float, Currency, and Any.

Specialized Conversions

· The System.Convert class provides methods that convert from one data type to another.

· System.Convert methods include ToBoolean, ToDouble, ToSingle, ToByte, ToInt16, ToString, ToChar, ToInt32, ToUInt16, ToDateTime, ToInt64, ToUInt32, ToDecimal, ToSByte, and ToUInt64.

· The System.BitConverter class converts data to and from arrays of bytes.

· Boxing occurs when you convert a value type into a reference type as in object obj = 72. This is slow, so you should avoid it if possible.

· Unboxing occurs when you convert a reference type back into a value type.

· The dynamic type is a static type, but its value isn’t evaluated until run time.

Strings

· Strings are immutable.

· The intern pool holds an instance of every unique String.

· StringBuilders are mutable and can be more efficient than Strings for performing a long series of concatenations.

· The StringWriter and StringReader classes provide methods for writing and reading characters and lines with an underlying StringBuilder object.

Formatting

· The ToString and String.Format methods convert values into strings.

· String.Format uses composite format strings that can specify argument numbers, field widths, alignments, and format strings. Field indexes start at 0.

· Standard format strings are locale-aware so you should use them whenever possible.

· Useful standard numeric formatting strings include C/c (currency), D/d (decimal), E/e (exponential), F/f (fixed point), G/g (the shorter of E or F), N/n (number, as in 1,234.56), P/p (percent), and X/x (hexadecimal).

· Useful standard DateTime formatting strings include d (short date), D (long date), f (“full” with short time), F (“full” with long time), g (“general” with short time), G (“general” with long time), M or m (month/day), t (short time), T (long time), and Y or y (year/month).

Review of Key Terms

boxing Boxing is the process of converting a value type such as int or bool into an object or an interface supported by the value’s type. This enables a program to treat a simple value as if it were an object. See also unboxing.

Common Language Runtime (CLR) A virtual machine that manages execution of C# (and other .NET) programs.

composite format A format item used by String.Format to indicate how an argument should be formatted. The basic syntax is {index[,length][:formatString]}.

custom formatting string Enable you to build formats that are not provided by the standard formatting strings.

explicit conversion In an explicit conversion, the code uses an operator (such as a cast) or method (such as int.Parse) to explicitly tell the program how to convert a value from one type to another.

immutable A data type is immutable if its value cannot be changed after it has been created. The String class is immutable. String methods that seem to modify a String, such as Replace and ToUpper, actually replace the String with a new value containing the modified contents.

implicit conversion In an implicit conversion, the program automatically converts a value from one data type to another without any extra statements to tell it to make the conversion.

intern pool The CLR maintains a table called “intern pool” that contains a single reference to every unique string used by the program.

interoperability Interoperability enables managed code (such as a C# program) to use classes provided by unmanaged code that was not written under the control of the CLR.

narrowing conversion A narrowing conversion is a data type conversion where the destination type cannot hold every possible value provided by the source data type. Converting from a long to an int is a narrowing conversion because a long can hold values such as 4,000,000,000 that cannot fit in an int. Narrowing conversions must be explicit.

standard formatting string Enables you to determine how you want a value displayed at a high level.

unboxing Unboxing is the processing of converting a boxed value back into its original value type value. See also boxing.

Unicode Unicode is a standard for encoding characters used by scripts in various locales around the world. It enables a program to display English, Chinese, Kanji, Arabic, Cyrillic, and other character sets. The .NET Framework uses the UTF-16 encoding, which uses 16 bits to represent each character.

widening conversion A widening conversion is a data type conversion where the destination type can hold any value provided by the source data type; although, some loss of precision may occur. For example, converting from an int to a long is a widening conversion.

Exam Tips and Tricks

The Review of Key Terms and the Cheat Sheet for this chapter can be printed off to help you study. You can find these files in the ZIP file for this chapter at www.wrox.com/remtitle.cgi?isbn=1118612094 on the Download Code tab.