2. Data Types

From Chapter 1’s HelloWorld program, you got a feel for the C# language, its structure, basic syntax characteristics, and how to write the simplest of programs. This chapter continues to discuss the C# basics by investigating the fundamental C# types.

A mind map shows the contents of chapter 2, which is related to data types.

Until now, you have worked with only a few built-in data types, with little explanation. In C# thousands of types exist, and you can combine types to create new types. A few types in C#, however, are relatively simple and are considered the building blocks of all other types. These types are the predefined types. The C# language’s predefined types include eight integer types, two binary floating-point types for scientific calculations and one decimal float for financial calculations, one Boolean type, and a character type. This chapter investigates these types and looks more closely at the string type.

Fundamental Numeric Types

The basic numeric types in C# have keywords associated with them. These types include integer types, floating-point types, and a special floating-point type called decimal to store large numbers with no representation error.

Integer Types

There are eight C# integer types, as shown in Table 2.1. This variety allows you to select a data type large enough to hold its intended range of values without wasting resources.

Table 2.1: Integer Types

Type

Size

Range (Inclusive)

BCL Name

Signed

Literal Suffix

sbyte

8 bits

–128 to 127

System.SByte

Yes

 

byte

8 bits

0 to 255

System.Byte

No

 

short

16 bits

–32,768 to 32,767

System.Int16

Yes

 

ushort

16 bits

0 to 65,535

System.UInt16

No

 

int

32 bits

–2,147,483,648 to 2,147,483,647

System.Int32

Yes

 

uint

32 bits

0 to 4,294,967,295

System.UInt32

No

U or u

long

64 bits

–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

System.Int64

Yes

L or l

ulong

64 bits

0 to 18,446,744,073,709,551,615

System.UInt64

No

UL or ul

 

Included in Table 2.1 (and in Tables 2.2 and 2.3) is a column for the full name of each type; we discuss the literal suffix later in the chapter. All the fundamental types in C# have both a short name and a full name. The full name corresponds to the type as it is named in the Base Class Library (BCL). This name, which is the same across all languages, uniquely identifies the type within an assembly. Because of the fundamental nature of these types, C# also supplies keywords as short names or abbreviations for the full names of fundamental types. From the compiler’s perspective, both names refer to the same type, producing identical code. In fact, an examination of the resultant Common Intermediate Language (CIL) code would provide no indication of which name was used.

Although C# supports using both the full BCL name and the keyword, as developers we are left with the choice of which to use when. Rather than switching back and forth, it is better to use one or the other consistently. For this reason, C# developers generally use the C# keyword form—choosing, for example, int rather than System.Int32 and string rather than System.String (or a possible shortcut of String).

The choice for consistency frequently may be at odds with other guidelines. For example, given the guideline to use the C# keyword in place of the BCL name, there may be occasions when you find yourself maintaining a file (or library of files) with the opposite style. In these cases, it would be better to stay consistent with the previous style than to inject a new style and inconsistencies in the conventions. Even so, if the “style” was a bad coding practice that was likely to introduce bugs and obstruct successful maintenance, by all means correct the issue throughout.

Begin 8.0

Floating-Point Types (float, double)

Floating-point numbers have varying degrees of precision, and binary floating-point types can represent numbers exactly only if they are a fraction with a power of 2 as the denominator. If you were to set the value of a floating-point variable to be 0.1, it could very easily be represented as 0.0999999999999999 or 0.10000000000000001 or some other number very close to 0.1. Similarly, setting a variable to a large number such as Avogadro’s number, 6.02 × 1023, could lead to a representation error of approximately 108, which after all is a tiny fraction of that number. The accuracy of a floating-point number is in proportion to the magnitude of the number it represents. A floating-point number is precise to a certain number of significant digits, not by a fixed value such as ±0.01. Starting with .NET Core 3.0, there are at most 17 significant digits for a double and 9 significant digits for a float (assuming the number wasn’t converted from a string as described in the Advanced Block: Floating-Point Types Dissected). 1

1. Prior to .NET Core 3.0, the number of bits (binary digits) converts to 15 decimal digits, with a remainder that contributes to a sixteenth decimal digit as expressed in Table 2.2. Specifically, numbers between 1.7 × 10307 and less than 1 × 10308 have only 15 significant digits. However, numbers ranging from 1 × 10308 to 1.7 × 10308 will have 16 significant digits. A similar range of significant digits occurs with the decimal type as well.

C# supports the two binary floating-point number types listed in Table 2.2. Binary numbers appear as base 10 (denary) numbers for human readability.

Table 2.2: Floating-Point Types

Type

Size

Range (Inclusive)

BCL Name

Significant Digits

Literal Suffix

float

32 bits

±1.5 × 10−45 to ±3.4 × 1038

System.Single

7

F or f

double

64 bits

±5.0 × 10−324 to ±1.7 × 10308

System.Double

15–16

D or d

 

End 8.0

A decimal is represented by ±N * 10k where the following is true:

  • N, the mantissa, is a positive 96-bit integer.

  • k, the exponent, is given by -28 <= k <= 0.

In contrast, a binary float is any number ±N * 2k where the following is true:

  • N is a positive 24-bit (for float) or 53-bit (for double) integer.

  • k is an integer ranging from -149 to +104 for float and from -1074 to +970 for double.

Decimal Type

C# also provides a decimal floating-point type with 128-bit precision (see Table 2.3). This type is suitable for financial calculations.

Table 2.3: Decimal Type

Type

Size

Range (Inclusive)

BCL Name

Significant Digits

Literal Suffix

decimal

128 bits

1.0 × 10−28 to approximately 7.9 × 1028

System.Decimal

28–29

M or m

 

Unlike binary floating-point numbers, the decimal type maintains exact accuracy for all denary numbers within its range. With the decimal type, therefore, a value of 0.1 is exactly 0.1. However, while the decimal type has greater precision than the floating-point types, it has a smaller range. Thus, conversions from floating-point types to the decimal type may result in overflow errors. Also, calculations with decimal are slightly (generally imperceptibly) slower.

Literal Values

A literal value is a representation of a constant value within source code. For example, if you want to have System.Console.WriteLine() print out the integer value 42 and the double value 1.618034, you could use the code shown in Listing 2.1.

Listing 2.1: Specifying Literal Values

System.Console.WriteLine(42);

System.Console.WriteLine(1.618034);

Output 2.1 shows the results of Listing 2.1.

Output 2.1

42
1.618034

By default, when you specify a literal number with a decimal point, the compiler interprets it as a double type. Conversely, a literal value with no decimal point generally defaults to an int, assuming the value is not too large to be stored in a 32-bit integer. If the value is too large, the compiler will interpret it as a long. Furthermore, the C# compiler allows assignment to a numeric type other than an int, assuming the literal value is appropriate for the target data type. short s = 42 and byte b = 77 are allowed, for example. However, this is appropriate only for constant values; b = s is not allowed without additional syntax, as discussed in the section “Conversions between Data Types” later in this chapter.

As previously discussed in this section, there are many different numeric types in C#. In Listing 2.2, a literal value is placed within C# code. Since numbers with a decimal point will default to the double data type, the output, shown in Output 2.2, is 1.61803398874989 (the last digit, 5, is missing), corresponding to the expected accuracy of a double.

Listing 2.2: Specifying a Literal double

System.Console.WriteLine(1.618033988749895);

Output 2.2

1.61803398874989

To view the intended number with its full accuracy, you must declare explicitly the literal value as a decimal type by appending an M (or m) (see Listing 2.3 and Output 2.3).

Listing 2.3: Specifying a Literal decimal

System.Console.WriteLine(1.618033988749895M);

Output 2.3

1.61803398874985

Now the output of Listing 2.3 is as expected: 1.618033988749895. Note that d is the abbreviation for double. To remember that m should be used to identify a decimal, remember that “m is for monetary calculations.”

You can also add a suffix to a value to explicitly declare a literal as a float or double by using the F and D suffixes, respectively. For integer data types, the suffixes are U, L, LU, and UL. The type of an integer literal can be determined as follows:

  • Numeric literals with no suffix resolve to the first data type that can store the value, in this order: int, uint, long, and ulong.

  • Numeric literals with the suffix U resolve to the first data type that can store the value, in the order uint and then ulong.

  • Numeric literals with the suffix L resolve to the first data type that can store the value, in the order long and then ulong.

  • If the numeric literal has the suffix UL or LU, it is of type ulong.

Note that suffixes for literals are case insensitive. However, uppercase is generally preferred to avoid any ambiguity between the lowercase letter l and the digit 1.

Begin 7.0

On occasion, numbers can get quite large and difficult to read. To overcome the readability problem, C# 7.0 added support for a digit separator, an underscore (_), when expressing a numeric literal, as shown in Listing 2.4.

Listing 2.4: Specifying Digit Separator

System.Console.WriteLine(9_814_072_356);

In this case, we separate the digits into thousands (threes), but this is not required by C#. You can use the digit separator to create whatever grouping you like as long as the underscore occurs between the first and last digits. In fact, you can even have multiple underscores side by side—with no digit between them.

End 7.0

In addition, you may wish to use exponential notation instead of writing out several zeroes before or after the decimal point (whether using a digit separator or not). To use exponential notation, supply the e or E infix, follow the infix character with a positive or negative integer number, and complete the literal with the appropriate data type suffix. For example, you could print out Avogadro’s number as a float, as shown in Listing 2.5 and Output 2.4.

Listing 2.5: Exponential Notation

System.Console.WriteLine(6.023E23F);

Output 2.4

6.023E+23

In all discussions of literal numeric values so far, we have covered only base 10 type values. C# also supports the ability to specify hexadecimal values. To specify a hexadecimal value, prefix the value with 0x and then use any hexadecimal series of digits, as shown in Listing 2.6.

Listing 2.6: Hexadecimal Literal Value

// Display the value 42 using a hexadecimal literal
System.Console.WriteLine(0x002A);

Output 2.5 shows the results of Listing 2.6. Note that this code still displays 42, not 0x002A.

Output 2.5

42
Begin 7.0

Starting with C# 7.0, you can also represent numbers as binary values (see Listing 2.7).

Listing 2.7: Binary Literal Value

// Display the value 42 using a binary literal
System.Console.WriteLine(0b101010);

The syntax is like the hexadecimal syntax except with 0b as the prefix (an uppercase B is also allowed). See the Beginner Topic: Bits and Bytes in Chapter 4 for an explanation of binary notation and the conversion between binary and decimal.

Note that starting with C# 7.2, you can place the digit separator after the x for a hexadecimal literal or the b for a binary literal.

End 7.0

More Fundamental Types

The fundamental types discussed so far are numeric types. C# includes some additional types as well: bool, char, and string.

Boolean Type (bool)

Another C# primitive is a Boolean or conditional type, bool, which represents true or false in conditional statements and expressions. Allowable values are the keywords true and false. The BCL name for bool is System.Boolean. For example, to compare two strings in a case-insensitive manner, you call the string.Compare() method and pass a bool literal true (see Listing 2.10).

Listing 2.10: A Case-Insensitive Comparison of Two Strings

string option;
...
int comparison = string.Compare(option, "/Help", true);

In this case, you make a case-insensitive comparison of the contents of the variable option with the literal text /Help and assign the result to comparison.

Although theoretically a single bit could hold the value of a Boolean, the size of bool is 1 byte.

Character Type (char)

A char type represents 16-bit characters whose set of possible values are drawn from the Unicode character set’s UTF-16 encoding. A char is the same size as a 16-bit unsigned integer (ushort), which represents values between 0 and 65,535. However, char is a unique type in C# and code should treat it as such.

The BCL name for char is System.Char.

To construct a literal char, place the character within single quotes, as in 'A'. Allowable characters comprise the full range of keyboard characters, including letters, numbers, and special symbols.

Some characters cannot be placed directly into the source code and instead require special handling. These characters are prefixed with a backslash () followed by a special character code. In combination, the backslash and special character code constitute an escape sequence. For example, represents a newline and represents a tab. Since a backslash indicates the beginning of an escape sequence, it can no longer identify a simple backslash; instead, you need to use \ to represent a single backslash character.

Listing 2.11 writes out one single quote because the character represented by ' corresponds to a single quote.

Listing 2.11: Displaying a Single Quote Using an Escape Sequence

class SingleQuote
{
  static void Main()
  {
      System.Console.WriteLine(''');
  }
}

In addition to showing the escape sequences, Table 2.4 includes the Unicode representation of characters.

Table 2.4: Escape Characters

Escape Sequence

Character Name

Unicode Encoding

'

Single quote

u0027

"

Double quote

u0022

\

Backslash

u005C

Null

u0000

a

Alert (system beep)

u0007



Backspace

u0008

f

Form feed

u000C

Line feed (sometimes referred to as a newline)

u000A

Carriage return

u000D

Horizontal tab

u0009

v

Vertical tab

u000B

uxxxx

Unicode character in hex

u0029

x[n][n][n]n

Unicode character in hex (first three placeholders are options); variable-length version of uxxxx

u3A

Uxxxxxxxx

Unicode escape sequence for creating surrogate pairs

UD840DC01

()

 

You can represent any character using Unicode encoding. To do so, prefix the Unicode value with u. You represent Unicode characters in hexadecimal notation. The letter A, for example, is the hexadecimal value 0x41. Listing 2.12 uses Unicode characters to display a smiley face (:)), and Output 2.8 shows the results.

Listing 2.12: Using Unicode Encoding to Display a Smiley Face

System.Console.Write('u003A');

System.Console.WriteLine('u0029');

Output 2.8

:)

Strings

A finite sequence of zero or more characters is called a string. The string type in C# is string, whose BCL name is System.String. The string type includes some special characteristics that may be unexpected to developers familiar with other programming languages. In addition to the string literal format discussed in Chapter 1, strings include a “verbatim string” prefix character of @, support for string interpolation with the $ prefix character, and the potentially surprising fact that strings are immutable.

Literals

You can enter a literal string into code by placing the text in double quotes ("), as you saw in the HelloWorld program. Strings are composed of characters, and consequently, character escape sequences can be embedded within a string.

In Listing 2.13, for example, two lines of text are displayed. However, instead of using System.Console.WriteLine(), the code listing shows System.Console.Write() with the newline character, . Output 2.9 shows the results.

Listing 2.13: Using the Character to Insert a Newline

class DuelOfWits
{
  static void Main()
  {
      System.Console.Write(
          ""Truly, you have a dizzying intellect."");
      System.Console.Write("
"Wait 'til I get going!"
");
  }
}

Output 2.9

"Truly, you have a dizzying intellect."

"Wait 'til I get going!"

The escape sequence for double quotes differentiates the printed double quotes from the double quotes that define the beginning and end of the string.

In C#, you can use the @ symbol in front of a string to signify that a backslash should not be interpreted as the beginning of an escape sequence. The resultant verbatim string literal does not reinterpret just the backslash character. Whitespace is also taken verbatim when using the @ string syntax. The triangle in Listing 2.14, for example, appears in the console exactly as typed, including the backslashes, newlines, and indentation. Output 2.10 shows the results.

Listing 2.14: Displaying a Triangle Using a Verbatim String Literal

class Triangle
{
  static void Main()
  {
      System.Console.Write(@"begin
             /
            /  
           /    
          /      
         /________
end");
  }
}

Output 2.10

begin
             /
            /  
           /    
          /      
         /________
end

Without the @ character, this code would not even compile. In fact, even if you changed the shape to a square, eliminating the backslashes, the code still would not compile because a newline cannot be placed directly within a string that is not prefaced with the @ symbol.

The only escape sequence the verbatim string does support is "", which signifies double quotes and does not terminate the string.

If the same literal string appears within an assembly multiple times, the compiler will define the string only once within the assembly and all variables will refer to the same string. That way, if the same string literal containing thousands of characters was placed multiple times into the code, the resultant assembly would reflect the size of only one of them.

Begin 6.0
String Interpolation

As discussed in Chapter 1, strings can support embedded expressions when using the string interpolation format starting in C# 6.0. The string interpolation syntax prefixes a verbatim string literal with a dollar symbol and then embeds the expressions within curly brackets. The following is an example:

System.Console.WriteLine($"Your full name is {firstName} {lastName}.");

where firstName and lastName are simple expressions that refer to variables.

Begin 8.0

Note that verbatim string literals can be combined with string interpolation by specifying the $ prior to the @ symbol (or @$"..." starting in C# 8.0), as in this example:

System.Console.WriteLine($@"Your full name is:
    { firstName } { lastName }");
End 8.0

Since this is a verbatim string literal, the text is output on two lines. You can, however, make a similar line break in the code without incurring a line break in the output by placing the line feeds inside the curly braces as follows:

System.Console.WriteLine($@"Your full name is: {
    firstName } { lastName }");

Note that the @ symbol is still required even when only placing the new lines within the curly braces.

End 6.0
String Methods

The string type, like the System.Console type, includes several methods. There are methods, for example, for formatting, concatenating, and comparing strings.

The Format() method in Table 2.5 behaves similarly to the Console.Write() and Console.WriteLine() methods, except that instead of displaying the result in the console window, string.Format() returns the result to the caller. Of course, with string interpolation, the need for string.Format() is significantly reduced (except for localization support). Under the covers, however, string interpolation compiles down a CIL combination of constants plus string.Concat() and string.Format() invocations.

All of the methods in Table 2.5 are static. This means that, to call the method, it is necessary to prefix the method name (e.g., Concat) with the type that contains the method (e.g., string). As illustrated later in this chapter, however, some of the methods in the string class are instance methods. Instead of prefixing the method with the type, instance methods use the variable name (or some other reference to an instance). Table 2.6 shows a few of these methods, along with examples of their use.

Table 2.5: string Static Methods

Statement

Example

static string string.Format(
    string format,
    ...)
string text, firstName, lastName;
//...
text = string.Format("Your full name is {0} {1}.",
  firstName, lastName);
// Display
// "Your full name is <firstName> <lastName>."
System.Console.WriteLine(text);
static string string.Concat(
    string str0,
    string str1)
string text, firstName, lastName;
//...
text = string.Concat(firstName, lastName);
// Display "<firstName><lastName>", notice
// that there is no space between names
System.Console.WriteLine(text);
static int string.Compare(
    string str0,
    string str1)
string option;
//...
// String comparison in which case matters
int result = string.Compare(option, "/help");
 
// Display:
//     0 if equal
//     negative if option < /help
//     positive if option > /help
System.Console.WriteLine(result);

 

string option;
//...
// Case-insensitive string comparison
int result = string.Compare(
     option, "/Help", true);
 
// Display:
//     0 if equal
//     < 0 if option < /help
//     > 0 if option > /help
System.Console.WriteLine(result);

 

Table 2.6: string Methods

Statement

Example

bool StartsWith(
    string value)
bool EndsWith(
    string value)
string lastName
//...
bool isPhd = lastName.EndsWith("Ph.D.");
bool isDr = lastName.StartsWith("Dr.");
string ToLower()
string ToUpper()
string severity = "warning";
// Display the severity in uppercase
System.Console.WriteLine(severity.ToUpper());
string Trim()
string Trim(...)
string TrimEnd()
string TrimStart()
// Remove any whitespace from
// both the start and end
username = username.Trim();

 

string text =
  "indiscriminate bulletin";
// Remove 'i' and 'n' from both the start or end
text = text.Trim("in".ToCharArray());
// Display: discriminate bullet
System.Console.WriteLine(text);
string Replace(
    string oldValue,
    string newValue)
string filename;
//...
// Remove ?'s from anywhere in the string
filename = filename.Replace("?", "");;

 

 

Begin 6.0
End 6.0
String Formatting

Whether you use string.Format() or the C# 6.0 string interpolation feature to construct complex formatting strings, a rich and complex set of composite formatting patterns is available to display numbers, dates, times, time spans, and so on. For example, if price is a variable of type decimal, then string.Format("{0,20:C2}", price) and the equivalent interpolation $"{price,20:C2}" both convert the decimal value to a string using the default currency formatting rules, rounded to two figures after the decimal place and right-justified in a 20-character-wide string. Space does not permit a detailed discussion of all the possible formatting strings; consult the MSDN documentation on composite formatting (http://itl.tc/CompositeFormatting) for a complete listing of possibilities.

If you want an actual left or right curly brace inside an interpolated string or formatted string, you can double the brace to indicate that it is not introducing a pattern. For example, the interpolated string $"{{ {price:C2} }}" might produce the string "{ $1,234.56 }".

Newline

When writing out a newline, the exact characters for the newline will depend on the operating system on which you are executing. On Microsoft Windows operating systems, the newline is the combination of both the carriage return ( ) and line feed ( ) characters, while a single line feed is used on UNIX. One way to overcome the discrepancy between operating systems is simply to use System.Console.WriteLine() to output a blank line. Another approach, which is almost essential for working with newlines from the same code base on multiple operating systems, is to use System.Environment.NewLine. In other words, System.Console.WriteLine("Hello World") and System.Console.Write($"Hello World{System.Environment.NewLine}") are equivalent. However, on Windows, System.WriteLine() and System.Console.Write(System.Environment.NewLine) are equivalent to System.Console.Write(" ")—not System.Console.Write(" "). In summary, rely on System.WriteLine() and System.Environment.NewLine rather than to accommodate Windows-specific operating system idiosyncrasies with the same code that runs on Linux and iOS.

String Length

To determine the length of a string, you use a string member called Length. This particular member is called a read-only property. As such, it cannot be set, nor does calling it require any parameters. Listing 2.16 demonstrates how to use the Length property, and Output 2.11 shows the results.

Listing 2.16: Using string’s Length Member

class PalindromeLength
{
  static void Main()
  {
      string palindrome;
      System.Console.Write("Enter a palindrome: ");
      palindrome = System.Console.ReadLine();
      System.Console.WriteLine(
          $"The palindrome "{palindrome}" is"
          + $" {palindrome.Length} characters.");
  }
}

Output 2.11

Enter a palindrome: Never odd or even
The palindrome "Never odd or even" is 17 characters.

The length for a string cannot be set directly; it is calculated from the number of characters in the string. Furthermore, the length of a string cannot change because a string is immutable.

Strings Are Immutable

A key characteristic of the string type is that it is immutable. A string variable can be assigned an entirely new value, but there is no facility for modifying the contents of a string. It is not possible, therefore, to convert a string to all uppercase letters. It is trivial to create a new string that is composed of an uppercase version of the old string, but the old string is not modified in the process. Consider Listing 2.17 as an example.

Listing 2.17: Error; string Is Immutable

class Uppercase
{
  static void Main()
  {
      string text;

      System.Console.Write("Enter text: ");
      text = System.Console.ReadLine();

      // UNEXPECTED:  Does not convert text to uppercase  
      text.ToUpper();                                                          

      System.Console.WriteLine(text);
  }
}

Output 2.12 shows the results of Listing 2.17.

Output 2.12

Enter text: This is a test of the emergency broadcast system.
This is a test of the emergency broadcast system.

At a glance, it would appear that text.ToUpper() should convert the characters within text to uppercase. However, strings are immutable and, therefore, text.ToUpper() will make no such modification. Instead, text.ToUpper() returns a new string that needs to be saved into a variable or passed to System.Console.WriteLine() directly. The corrected code is shown in Listing 2.18, and its output is shown in Output 2.13.

Listing 2.18: Working with Strings

class Uppercase
{
  static void Main()
  {
      string text, uppercase;

      System.Console.Write("Enter text: ");
      text = System.Console.ReadLine();
      // Return a new string in uppercase
      uppercase = text.ToUpper();                        

      System.Console.WriteLine(uppercase);
  }
}

Output 2.13

Enter text: This is a test of the emergency broadcast system.
THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM.

If the immutability of a string is ignored, mistakes like those shown in Listing 2.17 can occur with other string methods as well.

To actually change the value of text, assign the value from ToUpper() back into text, as in the following code:

text = text.ToUpper();
System.Text.StringBuilder

If considerable string modification is needed, such as when constructing a long string in multiple steps, you should use the data type System.Text.StringBuilder rather than string. The StringBuilder type includes methods such as Append(), AppendFormat(), Insert(), Remove(), and Replace(), some of which are also available with string. The key difference, however, is that with StringBuilder these methods will modify the data in the StringBuilder itself and will not simply return a new string.

null and void

Two additional keywords relating to types are null and void. The null value, identified with the null keyword, indicates that the variable does not refer to any valid object. void is used to indicate the absence of a type or the absence of any value altogether.

null

null can also be used as a type of “literal.” The value null indicates that a variable is set to nothing. Code that sets a variable to null explicitly assigns a “nothing” value. In fact, it is even possible to check whether a variable refers to a null value.

Assigning the value null is not equivalent to not assigning it at all. In other words, a variable that has been assigned null has still been set, whereas a variable with no assignment has not been set and, therefore, will often cause a compile error if used prior to assignment.

Note that assigning the value null to a string variable is distinctly different from assigning an empty string, "". Use of null indicates that the variable has no value, whereas "" indicates that there is a value—an empty string. This type of distinction can be quite useful. For example, the programming logic could interpret a homePhone of null to mean that the home phone number is unknown, while a homePhone value of "" could indicate that there is no home phone number.

Begin 8.0
Begin 2.0
End 2.0
The void “Type”

Sometimes the C# syntax requires a data type to be specified, but no data is passed. For example, if no return from a method is needed, C# allows you to specify void as the data type instead. The declaration of Main within the HelloWorld program (Listing 1.1) is an example. The use of void as the return type indicates that the method is not returning any data and tells the compiler not to expect a value. void is not a data type per se but rather an indication that there is no data being returned.

Conversions between Data Types

Given the thousands of types predefined in the .NET framework(s) and the unlimited number of types that code can define, it is important that types support conversion from one type to another where it makes sense. The most common operation that results in a conversion is casting.

Consider the conversion between two numeric types: converting from a variable of type long to a variable of type int. A long type can contain values as large as 9,223,372,036,854,775,808; however, the maximum size of an int is 2,147,483,647. As such, that conversion could result in a loss of data—for example, if the variable of type long contains a value greater than the maximum size of an int. Any conversion that could result in a loss of data (such as magnitude and/or precision) or an exception because the conversion failed requires an explicit cast. Conversely, a conversion operation that will not lose magnitude and will not throw an exception regardless of the operand types is an implicit conversion.

Explicit Cast

In C#, you cast using the cast operator. By specifying the type you would like the variable converted to within parentheses, you acknowledge that if an explicit cast is occurring, there may be a loss of precision and data, or an exception may result. The code in Listing 2.20 converts a long to an int and explicitly tells the system to attempt the operation.

Listing 2.20: Explicit Cast Example

Images

With the cast operator, the programmer essentially says to the compiler, “Trust me, I know what I am doing. I know that the value will fit into the target type.” Making such a choice will cause the compiler to allow the conversion. However, with an explicit conversion, there is still a chance that an error, in the form of an exception, might occur while executing if the data is not converted successfully. It is therefore the programmer’s responsibility to ensure the data is successfully converted, or else to provide the necessary error-handling code when the conversion fails.

You cannot convert any type to any other type simply because you designate the conversion explicitly using the cast operator. The compiler will still check that the operation is valid. For example, you cannot convert a long to a bool. No such conversion is defined and, therefore, the compiler does not allow such a cast.

Implicit Conversion

In other instances, such as when going from an int type to a long type, there is no loss of precision, and no fundamental change in the value of the type occurs. In these cases, the code needs to specify only the assignment operator; the conversion is implicit. In other words, the compiler is able to determine that such a conversion will work correctly. The code in Listing 2.24 converts from an int to a long by simply using the assignment operator.

Listing 2.24: Not Using the Cast Operator for an Implicit Conversion

int intNumber = 31416;
long longNumber = intNumber;

Even when no explicit cast operator is required (because an implicit conversion is allowed), it is still possible to include the cast operator (see Listing 2.25).

Listing 2.25: Using the Cast Operator for an Implicit Cast

int intNumber = 31416;
long longNumber = (long) intNumber;

Type Conversion without Casting

No conversion is defined from a string to a numeric type, so methods such as Parse() are required. Each numeric data type includes a Parse() function that enables conversion from a string to the corresponding numeric type. Listing 2.26 demonstrates this call.

Listing 2.26: Using float.Parse() to Convert a string to a Numeric Data Type

string text = "9.11E-31";
float kgElectronMass = float.Parse(text);

Another special type is available for converting one type to the next. This type is System.Convert, and an example of its use appears in Listing 2.27.

Listing 2.27: Type Conversion Using System.Convert

string middleCText = "261.626";
double middleC = System.Convert.ToDouble(middleCText);
bool boolean = System.Convert.ToBoolean(middleC);

System.Convert supports only a small number of types and is not extensible. It allows conversion from any of the types bool, char, sbyte, short, int, long, ushort, uint, ulong, float, double, decimal, DateTime, and string to any other of those types.

Furthermore, all types support a ToString() method that can be used to provide a string representation of a type. Listing 2.28 demonstrates how to use this method. The resultant output is shown in Output 2.17.

Listing 2.28: Using ToString() to Convert to a string

bool boolean = true;
string text = boolean.ToString();
// Display "True"
System.Console.WriteLine(text);

Output 2.17

True

For the majority of types, the ToString() method returns the name of the data type rather than a string representation of the data. The string representation is returned only if the type has an explicit implementation of ToString(). One last point to make is that it is possible to code custom conversion methods, and many such methods are available for classes in the runtime.

Begin 2.0
2.0
End 7.0

The key difference between Parse() and TryParse() is that TryParse() won’t throw an exception if it fails. Frequently, the conversion from a string to a numeric type depends on a user entering the text. It is expected, in such scenarios, that the user will enter invalid data that will not parse successfully. By using TryParse() rather than Parse(), you can avoid throwing exceptions in expected situations. (The expected situation in this case is that the user will enter invalid data, and we try to avoid throwing exceptions for expected scenarios.)

End 2.0

Summary

Even for experienced programmers, C# introduces several new programming constructs. For example, as part of the section on data types, this chapter covered the type decimal, which can be used to perform financial calculations without floating-point anomalies. In addition, the chapter introduced the fact that the Boolean type, bool, does not convert implicitly to or from the integer type, thereby preventing the mistaken use of the assignment operator in a conditional expression. Other characteristics of C# that distinguish it from many of its predecessors are the @ verbatim string qualifier, which forces a string to ignore the escape character; string interpolation, which makes code easier to read by embedding it into the string; and the immutable nature of the string data type.

In Chapter 3, we continue the topic of data types by elaborating more on the two types of data types: value types and reference types. In addition, we look at combining data elements together into tuples and arrays.

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

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