Simple Types

   


Simple types are also called primitive types and belong to a group of built-in predefined types found in C#. Examples of such values are single numbers (the int type) and individual characters.

Our path to appreciating the variety of simple types provided begins with an understanding of the relationship between the information you want to store from the "real" world and the variables in your source code.

Variables constitute a central part of a C# program. They enable the programmer to symbolically represent values stored in the computer memory. In fact, the identifier of a variable (such as count or age) is a symbol representing an underlying value somewhere in memory. A name, such as taxPercentage, is far easier to understand in a piece of source code than a memory address such as "4 bytes at address 3024."

Variables enable a program to absorb input from external sources, such as users and databases, and provide easy access to those data in your source code. By manipulating these variables, it becomes possible to eventually extract valuable information from the program in the form of output.

Programmers need to store many different kinds of information from the real world (and from results of temporary calculations in the programs) in the variables of their computer programs. Some examples are the average yearly rainfall in San Francisco, the number of newborn babies in Paris in 1996, the height of a particular door in an architectural drawing of a CAD program, or the name of a famous movie star.

When we choose a variable to be of a specific type, we are specifying the kind of value this variable can hold and the kind of operations in which it can be involved. Each simple type exposes specific characteristics in terms of

  • What it allows the variable to store Examples are integer numbers, floating-point numbers, and single characters.

  • Its size range An example size range for int is -2147483648 to 2147483647.

  • The amount of internal memory used The memory consumption can easily vary between 8 and 64 bits to represent one variable, depending on its type. For example, a variable of type int takes up 32 bits of memory.

  • The kind of operations that may be performed on it Earlier examples showing int values suited for addition and string values suited for concatenation illustrate this point.

Accordingly, it is important to choose a type well suited for the problem at hand. For example, the name of a famous actor could not be represented by an int variable, and the average rainfall in San Francisco would be hard to represent with a value of a type meant for characters.

Using types eating up superfluous amounts of internal memory is a problem often found when the characteristics of each piece of information has not been properly analyzed. Consider a person's age. Peoples' ages usually vary between 0 and approximately 120. So, if you need to store the age of each person in the State of California who receives Social Security benefits, why use a 32-bit int variable with the range of -2147483648 to 2147483647 if another 8-bit integer type exists with the range 0 255? For each one thousand people being stored, you would save 1,000 x (32-8) = 24,000 bits of memory, not to mention the advantage of the higher processing speeds that accompany the leaner 8-bit integer-based variable.

Fortunately, C# provides a rich assortment of simple types that make it possible to choose a suitable type to represent a number or a single character. The next section takes a closer look at each of these types.

Overview of the Simple Types

C# defines 13 simple types, which are listed in Table 6.2. I have chosen to give a complete overview at this early stage to provide a handy reference. You will likely return to this table many times while writing your programs.

Note

graphics/common.gif

Even though the bool type (last row of Table 6.2) is considered a simple type, it goes hand in hand with the control of flow.


Table 6.2. The Simple Types in C#
C# Keyword .NET CTS Type Kind Of Value Memory Used Range and Precision
sbyte System.SByte Integer 8 bits 128 to 127
byte System.Byte Integer 8 bits 0 to 255
short System.Int16 Integer 16 bits 32768 to 32767
ushort System.UInt16 Integer 16 bits 0 to 65535
int System.Int32 Integer 32 bits 2147483648 to 2147483647
uint System.UInt32 Integer 32 bits 0 to 4294967295
long System.Int64 Integer 64 bits 9223372036854775808 to 9223372036854775807
ulong System.UInt64 Integer 64 bits 0 to 18446744073709551615
char System.Char Integer (Single character) 16 bits All Unicode characters
float System.Single floating point 32 bits (+/-)1.5x10-45 to (+/-)3.4x1038 Approximately 7 significant digits
double System.Double floating point 64 bits (+/-)5.0x10-324 to (+/-)3.4x1030 15 16 significant digits
decimal System.Decimal High precision decimal number 128 bits (+/-)1.0x10-28 to (+/-)7.9x1028
bool System.Boolean true or false 1 bit Not applicable

Before I describe what each column represents, please identify the int type in the fifth row from the top. This should give you a good idea of the content of each column.

The following is a brief summary of what each column of Table 6.2 represents:

  • Keyword column Keyword refers to the symbol used in the C# source code when declaring a variable. An example is the familiar declaration statement using the keyword int.


    graphics/06infig06.gif

  • .NET CTS type column The System namespace of the .NET Framework contains all the simple types. Each of the keywords shown in the keyword column is an alias for the type it specifies in the .NET Common Type System (CTS). For example, the keyword int refers to its CTS name System.Int32. Thus, you could use either the short alias or the long full name in your source code. Accordingly, the following two statements are identical:


    graphics/06infig07.gif

    The latter is a cumbersome style, so the use of the alias is recommended.

  • The Kind of value column Specifies the four different groups of simple types contained in C# integer, floating-point, true/false, and high-precision. Briefly

    • Integers are whole numbers.

    • Floating-point numbers can represent numbers with fractions.

    • High-precision numbers also represent fractions but with higher precision.

    • bool type values can contain only two values true and false.

  • Memory used The amount of internal computer memory used by a value of each type. Notice that 8 bits equals 1 byte.

  • The Range and precision column Displays the range and precision a value of the corresponding type can hold. Notice that even though the char type is designed for single character values, it is regarded as being an integer type. More about this when I return to the char type later in this chapter.

When looking through the table, you will discover nine integer types in total. They differ from each other in three different ways the range, the amount of computer memory they occupy, and whether negative numbers can be stored.

You will also find the three types float, double, and decimal that are used to store numbers containing a fractional part, such as 6.87, 9.0, and 100.01. The main attributes setting them apart are their range, memory use, and precision.

Syntax Box 6.1 Syntax and the Variable Declaration Statement

The syntax of a computer language provides the rules to follow when writing a valid computer program. When you learn a computer language, you need to know its syntax. Until now, I have relied on descriptions (based on the English language) and examples to portray the syntax of C#. However, syntax is very exact (to the semicolon), so a description of it is enhanced by a similarly exact notation form. One such notation form will be described in this Syntax Box and used throughout the rest of the book in similarly marked Syntax Boxes.

Notice that this notation is not a substitute for examples and thorough descriptions in this book but should merely be regarded as an enhancement of these. The notation form is useful for quick references and will even enable you to learn new language constructs merely by looking through very few lines of syntax notation.

The notation form I have chosen to use is a simplified version of a notation form frequently used to describe computer language syntax called Backus-Naur Form or BNF. This form was originally developed by J. Backus and P. Naur for the definition of the Algol 60 language.

The syntax notation form consists of the following elements:

  • The ::= symbol that means "is defined by"

  • Meta variables (place holders) of the form <Word>

  • The optional symbol consisting of two square bracket [] that encloses optional items [<this is optional>]

  • Three periods … indicate an unlimited amount of items

  • The vertical line | used to indicate alternative items

The following text describes each element in more detail.

To precisely describe a construct of the C# language (for example, a variable declaration statement) is to define the construct. The symbol ::= (two semicolons immediately followed by an equals sign) is used to indicate "is defined by."

We also need a way to denote and represent words (such as keywords and identifiers) used in a C# program. To this, end we use symbols called meta variables or syntactic variables. They take the form <Word> and act as placeholders.

As an example, consider our familiar variable declaration statement:

 int myNumber; 

In the previous chapters, you have seen several examples of variable declaration statements. Examples are great teaching tools, but they don't provide for exact definitions, such as the following notation:

 Variable_declaration_statement  ::=                      <Type> <Variable_identifier>; 

In this case, the ::= indicates that what follows is a definition of a variable_declaration_ statement. <Type> is a syntactic variable and can be replaced by int, string, or any valid type name in C#. For example, all the keywords of each simple type listed in Table 6.2 could take the place of <Type>.

The second syntactic variable, <Variable_identifier>, can be replaced by a variable name such as myNumber.

Finally, the declaration statement must end with a semicolon.

The following gives you an example where the three periods ... and the optional symbol [] are needed to precisely define a C# construct.

Apart from declaring the two integer variables number1 and number2 as follows


graphics/06infig08.gif

we could have declared as many int variables as we wanted by using the following form:

 int number1, number2, number3,... ; 

The three dots ... between number3 and the semicolon indicate that we can declare as many int variables that we want.

Note that the following

 , number2, number3, ... 

is optional. Consequently, we can write the declaration statement more correctly as follows by using the optional symbol


graphics/06infig09.gif

Finally, we need to be able to express when several alternative items can be used in one position. As you have seen previously, the keywords public and private are used when declaring the methods and instance variables of a class, such as

 private int currentFloor; 

Even though it is regarded as poor programming practice, it is also possible to declare an instance variable to be public, as in the following:

 public int currentFloor; 

We could also leave out any of the access specifiers altogether and write

 int currentFloor; 

defaulting currentFloor to have private access.

Our ability to use either private or public is denoted by a vertical line (|) and can be expressed as private | public. Because the specification is optional, we can write [private | public], and the full variable declaration can now be defined as follows

 Variable_declaration_statement ::=          [public | private] <Type> <Variable1_Identifier> [,  graphics/ccc.gif<Variable2_Identifier> ...]; 

Sometimes a language element can be defined in different ways. For example as you will see later an expression can be defined not only as a literal but also as a variable identifier and a numerical expression. This can be expressed with our notation form by using several ::= symbols after expression as shown in the following lines:

 <Expression>            ::= <Literal>            ::= <Variable_Identifier>            ::= <Numerical_Expression> 

Note

graphics/common.gif

The contents of the Syntax Boxes are not targeted at advanced programmers who are going to write the source code for a C# compiler and who therefore need exact detailed information about every aspect of C#'s syntax. Instead they are meant as an aid to learn C# and to introduce the idea of syntax notation. For that reason, each definition does not cover all the gory details and possibilities about the particular C# construct it defines but is kept as simple and accessible as possible.


Syntax and Semantics

graphics/common.gif

Where syntax relates to the form of a language construct, semantics is concerned about the meaning of the construct. For example "adding myNumber together with yourNumber and assigning the result to ourSum" explains the semantics of the following statement

 ourSum = myNumber + yourNumber; 

whereas the syntax is about the symbols we use (=, +, ;) and how the variables are written and combined with these symbols.


Integer Types

C# defines nine integer types as shown in Table 6.2. Integers are whole numbers with no fractional part, such as 34, 0 and 7653. Because there are an infinite number of integers (877366272736362525636256277636525763592827262 is also an integer) and a finite amount of computer memory allocated with each simple type, we can only represent a subset (an extremely useful subset, fortunately) of all integers.

Each integer type uses a specific amount of internal memory. A larger range occupies more memory.

Some types (such as int), called signed types, can store both negative and positive values. Other types, called unsigned types, can represent only positive values (including zero). There are four signed types (sbyte, short, int, and long) and four unsigned types (byte, ushort, uint, and ulong).

Note

graphics/common.gif

The char type is also considered to be an integer type, despite its special properties dedicated to representing Unicode characters. Because char is the odd one out and goes hand-in-hand with the string type, I will first present the eight previously mentioned integers.


Bit Refresher

graphics/common.gif

A bit represents just two values 0 and 1. Accordingly, two bits can represent 2x2 different values = 4. 3 bits can represent 2x2x2 = 8 values. x bits can represent 2x values, and 8 bits can represent 28 = 256 values.

16 bits 65,536 values

32 bits 4,294,967,296 values

64 bits 18,446,744,073,709,551,616 value

For a detailed discussion about the binary number system and how it relates to other number systems, please refer to Appendix D, "Number Systems."


Signed Integer Types

The smallest signed integer type is sbyte. It occupies 8 bits and has a range from 128 to 127. Half of the 256 values that can be represented by an 8-bit based type are dedicated to negative values, the other half are dedicated to 0 and positive values.

sbyte variables are declared by using the keyword sbyte, as shown in the following:

 sbyte myNumber; 

You can find the other signed integer types in Table 6.2.

Tip

graphics/bulb.gif

When you start programming, int is good choice for most variables; it can be used for most purposes without consuming excessive amounts of memory. However, you should make it a habit to analyze each variable carefully and choose its type accordingly.


Unsigned Integer Types

Each of the four signed integer types has a corresponding unsigned type that consumes the same amount of memory as its counterpart. Because the negative part is eliminated, the memory to hold those values can be used to hold larger positive numbers instead. For example, sbyte can represent the range 128 to 127, whereas its unsigned counterpart byte can hold integers in the range 0 to 255 (127 + | 128|). Table 6.3 displays the signed integer types along with their unsigned counterparts.

Table 6.3. Signed Integer Types and Their Unsigned Counterparts
Signed Unsigned Memory Use
sbyte byte 8 bits
short ushort 16 bits
int uint 32 bits
long ulong 64 bits

Tip

graphics/bulb.gif

You should use unsigned types for amounts, which are never negative. Examples could be the number of births in a country in one year, the population of a city, and number of books in a library.


Integer Literals

Different from variables, literals cannot change their value. A literal number like 5 is always 5, never 3 or 8, so numbers such as 3, 1009, and 487 are all examples of literals.

All integer literals are of a specific type, just like all variables you declare must be of a certain type. The C# compiler follows precisely specified rules when determining the type of a literal. It will look at the size of the literal and an optional suffix immediately following the literal.

If no suffix is specified, the compiler will choose the first of the following types where the literal can "fit": int, uint, long, and ulong. If we combine this fact with the ranges provided in Table 6.2, we can deduct which literals are determined to be which types. This is specified in Figure 6.6.

Figure 6.6. Literals and their types no suffix.
graphics/06fig06.gif

According to Figure 6.6, the literal

  • 43200 is an int

  • 2507493742 is a uint

  • 25372936858775201 is a long

  • 270072036654375827 is a long

  • 17016748093204541685 is a ulong

U, L, or UL can be used to suffix a literal.

If a literal is suffixed by U (such as 75U), the compiler will choose the first of the types uint or ulong that can contain the value of the literal within its range. This is illustrated in Figure 6.7.

Figure 6.7. Literals and their types with the suffix U.
graphics/06fig07.gif

If the literal is suffixed by L (such as 453L), the compiler will choose the first of the long or ulong types that will contain the value of the literal. This is illustrated in Figure 6.8.

Figure 6.8. Literals and their types with the suffix L.
graphics/06fig08.gif

If the programmer writes the suffix UL after the literal, the compiler has no options; it specifies that the literal must be of type ulong.

Note

graphics/common.gif

The compiler lets you choose among a few options when typing the suffixes:

  • Instead of U, you can use u.

  • L and l (lowercase L) have the same meaning. However, because it is easy to confuse 1 (one) with l (lowercase L), L is recommended.

  • Many different options exist to represent UL. You can use any combination of upper- and lowercase U and L. For example, Ul, lu, and uL are all valid suffixes representing the same meaning.


C# allows you to write integer literals in two different number bases decimal numbers (base 10) and hexadecimal numbers (base 16). See Appendix D which can be found at www.samspublishing.com for a discussion of the various number bases. A base 10 literal begins with any of the digits 1 9, whereas 0x in front of the number indicates a hexadecimal number. For example, 99 is a base 10 number, but 0x99 is a hexadecimal number that is equal to 153 in base 10.


graphics/06infig10.gif

Integer literals cannot contain commas (32,000 is invalid) or decimal points (3.0 and 76.97 are both invalid).


graphics/06infig11.gif

To specify that an integer is negative, add a minus sign in front of the number, such as 30. If you want to emphasize that an integer is positive, you can put a + sign in front of the integer literal, but this is not required, so +55 is identical to 55.

Assignment Statements

An assignment statement, as stated in Chapter 3, assigns a value to a variable. For example, if populationSize is of type int, you can assign the value 387675 by using the following assignment statement:

 populationSize = 387675; 

When used in an assignment statement, the equals sign is called assignment operator. By using our newly introduced syntax notation form, we can write the general assignment statement for a simple type as follows:

Syntax Box 6.2 The Assignment Statement with Simple Types

 Assignment_statement ::=                    <Variable_Identifier> = <Expression> ; 

where

 <Expression>            ::= <Literal>            ::= <Variable_Identifier>            ::= <Numerical_Expression> 

Note that <Numerical_expression> refers to a valid combination of number values and operators. (count * 4) + (distance - 100) is a numerical expression.

An expression can be a literal, such as 3876, a variable like populationSize, or any valid combination of literals, variables, and operators. Consequently,

 6 + 5 

is an expression, just as

 (5 + populationSize) - (200 - populationSize) 

When the computer executes the assignment statement, the right side of the assignment operator is first evaluated; the resulting value is then assigned to the variable on the left side of the operator. For example, take the assignment statement shown in Figure 6.9, where distance1 has the value 100 and distance2 has the value 200.

Figure 6.9. Two steps in an assignment statement.
graphics/06fig09.gif

The computer will first add the two variables distance1 and distance2 together (see 1 in Figure 6.9) and then assign the result of 300 to totalDistance (see 2 in Figure 6.9).

Due to this sequence of events, it is possible and often useful to position identical variable identifiers on both sides of the assignment operator, as in the following:

 count = count + 1; 

At first glance, this assignment looks peculiar, but when executed in two sequences as previously shown, we realize that all we are effectively doing is adding 1 to the original value of count. Had the value of count prior to the statement been 100, it would now be 101. It is possible to replicate this logic with the minus sign ( ). For example, the following statement

 count = count - 1; 

will deduct 1 from the original value of count.

The two predefined operators (++ and --) are available to express these statements in a more compact fashion. The following statement will add 1 to the original value of the operand:


graphics/06infig12.gif

whereas,


graphics/06infig12a.gif

deducts 1 from the original value of count. The program in Listing 6.8 presented later, which is part of the Blipos Clock case study, makes use of these operators.

Integer Type Compatibilities

The large number of integer types available enables you to choose a suitable type for each variable in your program. However, many different types often coexist in the same program, and several types are often involved in the same statement. This can cause problems related to incompatible types. In the following section, I will concentrate on the issues related to the assignment statement.

To illustrate a typical situation involving different types in an assignment statement, let's have a look at the example in Listing 6.2.

Listing 6.2 Source Code of Compatibility.cs
01: //Compatible and incompatible types 02: 03: using System; 04: 05: public class Compatibility 06: { 07:     public static void Main() 08:     { 09:         int totalMinutesOnMobile; 10:         uint totalPopulation; 11:         ushort averageMinutesOnMobile; 12: 13:         totalPopulation = 347638; 14:         averageMinutesOnMobile = 10; 15: 16:          // uint cannot be implicitly converted to int. 17:         totalMinutesOnMobile = totalPopulation * averageMinutesOnMobile; 18: 19:         Console.WriteLine("Total Minutes On Mobiles: " + totalMinutesOnMobile); 20:     } 21: } 

The following compile error is reported:

 Compatibility.cs(17,32): error CS0029: Cannot implicitly convert type 'uint' to 'int' 

Suppose you have written this source code snippet for use in an application facilitating the research and strategic planning in the marketing department of a large mobile phone company. To represent the total number of people in a city, you have chosen a uint variable called totalPopulation (line 10). The source code further contains a variable of type ushort called averageMinutesOnMobile (line 11) representing the average approximate number of minutes each person in the city is talking on a mobile phone in one day. Notice how uint and ushort have been chosen to suit the information they represent.

You would now like to calculate the total number of minutes the whole population is on the mobile phone. This can be done by multiplying totalPopulation with averageMinutesOnMobile and then assigning the result to totalMinutesOnMobile expressed in C# (line 17) and shown in Figure 6.10.

Figure 6.10. Invalid statement due to incompatible types.
graphics/06fig10.gif

What happens if, as specified in line 9, we use the int type for totalMinutesOnMobile? Well, during compilation before the actual program has started running, the compiler is unaware of the values that will eventually be stored in our variables. Thus, it deduces that because totalPopulation is a uint and has a range of 0 to 4294967295, there is a legitimate chance that this variable might hold a large value, such as 4000000000. On the other hand, totalMinutesOnMobile has a range of 2147483648 to 2147483647 and is, therefore, not capable of holding the large value of 4000000000 we might assign to it from totalPopulation. The result would be a loss of data. Consequently, the compiler triggers the following compiler error:

 "Cannot implicitly convert type 'uint' to 'int' " 

even though our specific execution of the program would use population values considerably smaller than 2147483647.

If, instead of int, we had chosen the uint type for totalMinutesOnMobile (exchanging int in line 9 with uint), the compiler would have accepted the statement, despite the remaining difference between the source type ushort and the destination type uint.

Without hesitation, it would implicitly convert (automatic conversion performed by the compiler) the source type ushort with the range 0 to 65535 to the destination type uint with the larger range, because there is no danger of data loss. This is illustrated in Figure 6.11.

Figure 6.11. Implicit conversion of ushort to uint.
graphics/06fig11.gif

Note, though, that the result of the right-side multiplication could still exceed the upper limit (4294967295) of the destination variable. In the (unlikely) case of a city with a population of 4000000000 with an average mobile use of 10 minutes, the result of the multiplication would be 40000000000 and so it would exceed the capacity of totalMinutesOnMobile and trigger a runtime error. The compiler merely looks at each variable separately and does not take the actual mathematical calculation into consideration.

Incorrect assignments, similar to the kind previously described, can be of two types:

  • The source value might exceed the upper limit of the destination type. This is termed overflow; for example, assigning 300 to a variable of type byte will result in an overflow.

  • If the source value is less than the lower limit of the destination type, the result is an underflow; for example, assigning 40000 to a variable of type short results in an underflow.

Automatic Overflow/Underflow Checking During Runtime

It is possible to instruct the compiler to generate checks for overflow and underflow problems during runtime. This can be accomplished in the following way, illustrated by an example.

First, you need to type in the source code from Listing 6.3. Listing 6.3 is the result of changing the following lines of Listing 6.2:

  1. Line 5 to

     public class Overflow 
  2. Line 9 to

     uint totalMinutesOnMobile; 
  3. Line 13 to

     totalPopulation = 4000000000; 

By changing lines 9 and 13, we are causing an overflow. Then, when you instruct the compiler to compile the program, you need to include an additional command apart from the standard command you have used previously. Instead of merely typing

 C:\ >csc Overflow.cs <enter> 

you need to type

 C:\ >csc /checked+ Overflow.cs <enter> 

The /checked+ part is called a compiler command. It instructs the compiler to generate checks for overflow and underflow problems during runtime. Notice the + after /checked;, it says "Yes, do make the check," whereas a - after /checked (/checked-) switches off this kind of check.

Note

graphics/common.gif

If you don't provide the /checked compiler command when compiling, the setting defaults to /checked-.


Listing 6.3 Source Code for Overflow.cs
01: //Compatible and incompatible types 02: 03: using System; 04: 05: public class Overflow 06: { 07:     public static void Main() 08:     { 09:         uint totalMinutesOnMobile; 10:         uint totalPopulation; 11:         ushort averageMinutesOnMobile; 12: 13:         totalPopulation = 4000000000; 14:         averageMinutesOnMobile = 10; 15: 16:          // uint cannot be implicitly converted to int. 17:         totalMinutesOnMobile = totalPopulation * averageMinutesOnMobile; 18:  19:         Console.WriteLine("Total Minutes On Mobiles: " + totalMinutesOnMobile); 20:     } 21: } 

Now compile the program with the /checked+ command, shown earlier, and run the program. The program is prematurely interrupted by a runtime error and the following text is shown on the console:

 Exception occurred: System.OverflowException... etc. 

As expected, this is an OverflowException thrown by the runtime and caused by line 17 that attempted to assign 40000000000 to totalMinutesOnMobile.

Tip

graphics/bulb.gif

It is often useful to have the /checked option switched on (/checked+) when you design and write software. However, because it causes a slight loss in performance, it might not be appropriate for the released version of the program.


Note

graphics/common.gif

Exceptions are generated in C# when abnormal situations, such as an overflow or underflow, occur in your program.


The /checked+ compiler command will generate underflow and overflow checks for the entire compiled source code. However, you might want to control which parts of the program are always being checked and which parts are never checked regardless of the compiler commands given. To this end, you can apply the keywords checked and unchecked in the C# source code itself. They can be applied to create checked and unchecked blocks of code (in which case, they are referred to as statements) or be applied to single expressions (in which case, they act as operators).

Syntax Box 6.3 checked and unchecked Statements

 Checked_statement::=        checked        {              <Statements>        } Unchecked_statement::=        unchecked        {              <Statements>        } 

Note The checked and unchecked statements must reside inside a method body. They cause all statements (<statements>) in the block they designate to be evaluated in a checked and unchecked environment.

 Checked_expression::=          checked (<Expression>) Checked_expression::=          unchecked (<Expression>) 

Listing 6.4 illustrates the use of the checked keyword.

Listing 6.4 Source Code for TravelingFullCheck.cs
01: using System; 02: 03: class Traveling 04: { 05:     public static void Main() 06:     { 07:         checked 08:         { 09:             int totalTime; 10:             int totalEnergy; 11:             int totalRadiation; 12:             int distance = 2100000; 13:             int timeFactor = 100000; 14:             int energyFactor = 20; 15:             int radiationFactor = 40000; 16: 17:             totalTime = distance * timeFactor; 18:             totalEnergy = distance * energyFactor; 19:             totalRadiation = distance * radiationFactor; 20: 21:             Console.WriteLine("Total time: " + totalTime); 22:             Console.WriteLine("Total energy: " + totalEnergy); 23:             Console.WriteLine("Total radiation: " + totalRadiation); 24:         } 25:     } 26: } 

When the code contains the checked keyword in line 7, irrespective of the compiler setting, the following output results:

 Exception occurred: System.OverflowException... etc. 

If you substitute checked in line 7 with unchecked you will see the following odd looking output

 Total time: -453397504 Total energy: 42000000 Total radiation: -1899345920 

Listing 6.4 contains a checked block spanning from line 8 to line 24 with the keyword checked in line 7. The two statements in lines 17 and 19 contain overflowing expressions causing the OverflowException (with checked block) or the incorrect results as output (with unchecked block) shown in the sample output.

We could fine-tune Listing 6.4 by using the checked/unchecked operators instead of the checked/unchecked statements. This is shown in Listing 6.5.

Listing 6.5 Source Code for TravelingPartlyChecked.cs
01: using System; 02: 03: class Traveling 04: { 05:     public static void Main() 06:     { 07:         int totalTime; 08:         int totalEnergy; 09:         int totalRadiation; 10:         int distance = 2100000; 11:         int timeFactor = 100000; 12:         int energyFactor = 20; 13:         int radiationFactor = 40000; 14: 15:         totalTime = unchecked(distance * timeFactor); 16:         totalEnergy = checked(distance * energyFactor); 17:         totalRadiation = distance * radiationFactor; 18: 19:         Console.WriteLine("Total time: " + totalTime); 20:         Console.WriteLine("Total energy: " + totalEnergy); 21:         Console.WriteLine("Total radiation: " + totalRadiation); 22:     } 23: } 

If compiler option is set to /checked+

 Exception occurred: System.OverflowException... etc. 

If compiler option is set to /checked-

 Total time: -453397504 Total energy: 42000000 Total radiation: -1899345920 

The expression in line 15 of Listing 6.5 generates an overflow and is left unchecked; the expression in line 16 is checked but stays within the range of the int type, so no exceptions are generated from these two lines. Line 17 generates an overflow and has no specification. Consequently, an exception will be generated by line 17 only in the case when the source code is compiled with the /checked+ option.

An expression consisting merely of literals on the right side of the assignment operator, such as in line 2 shown next


graphics/06infig13.gif

can be fully evaluated and checked for overflow/underflow at compile time. The compiler checks all these expression at compile time, unless the expression is explicitly unchecked in the source code. If a problem is encountered, such as in the previous expression, a compiler warning or error will be generated during compile time

 warning CS0220: The operation overflows at compile time in checked mode. 

Don't Ignore Compiler Warnings

graphics/bulb.gif

Unlike a compiler error, a compiler warning does not prevent a program from being executed. However, warnings are useful hints to more-or-less obvious problems in your program. The best practice is to identify the problems and remove all compiler warnings.


The Implicit Conversion Path

The compiler has a very precise idea of which integer types it agrees to implicitly convert, based on a general rule deduced from the previous discussion and stated in the following Note.

When Are Implicit Conversions of Integer Types Performed?

graphics/common.gif

Every destination type accepted for an implicit conversion must be able to fully represent the range of the source type.


If we follow the rule of acceptable implicit conversions, we can draw a type hierarchy, shown in Figure 6.12, that indicates the possible implicit conversion pathways.

Figure 6.12. Implicit conversion paths for the integer types.
graphics/06fig12.gif

Not all paths have explicitly been shown with a single arrow. To determine whether an implicit conversion is acceptable, you might need to follow several arrows and go through several type boxes. For example, to convert from byte to long, you start at the byte box and follow the arrows over short and int and finally arrive at long.

Note

graphics/common.gif

Even though char has a larger range than the sbyte, byte, and ushort type, spanning across all their possible values, no implicit conversion path exists back to the char type from any of these types.


Overflow and Underflow Behavior of Integer Variables

In the previous section, you saw how the compiler could be instructed to check for overflow and underflow during runtime. But what happens if we switch this capability off and the program experiences an overflow or underflow?

To answer this question, we will move our attention to planet Blipos, where our alien friend from Chapter 2, "Your First C# Program" has invited us to visit him. In his invitation, which arrived by i-mail (intergalactic mail), he explains that the Bliposians have a very different way of measuring time than do humans on planet Earth. Instead of 60 seconds in a minute, 60 minutes in an hour, and 24 hours in day, they have 256 seconds in a minute and 65,535 minutes in a full day. One full day consists of 32,768 minutes of darkness displayed as negative values and 32,767 minutes with daylight displayed as positive values (notice how these numbers relate to the ranges of the byte and short types).

It turns out that the method by which the Bliposians measure seconds resembles the behavior of a variable of type byte in that both will return to the beginning of the range of values (0) whenever the end of the range has been reached (255). Similarly, when the minutes counter on a Blipos clock has reached 32,767, it will display 32768 the next second, which is comparable to the behavior of a variable of type short when it overflows. Figure 6.13 illustrates these points.

Figure 6.13. Overflow behavior for variables of type byte and short.
graphics/06fig13.gif

Overflow and Underflow of the byte and short Variables Overflow of byte and short

graphics/common.gif

byte Adding 1 to a variable of type byte with a value of 255 will result in a value of 0.

short Adding 1 to a variable of type short with a value of 32,767 will result in a value of 32,768.


Underflow byte and short

byte Deducting 1 from a variable of type byte with a value of 0 will result in a value of 255.

short Deducting 1 from a variable of type short with a value of 32,768 will result in a value of 32,767.

1 doesn't have to be exclusively used in these examples. For example, adding 8 to a variable of type byte containing a value of 255 will change its value to 7.

The remaining integer types follow the same logic of overflow and underflow with their relevant ranges substituted for the ranges of byte and short used in this presentation.


To get accustomed to the time measuring system used on planet Blipos, we decide to write a computer program resembling a Blipos clock. This program, presented in the following case study, not only illustrates how the byte and short integers handle overflow and underflow, it is also meant as an OOP and Software Development Process refresher. Additionally, it introduces a few useful features from C# not mentioned previously. These are local variables, two new comparison operators, the type cast, and the while loop. The introductions are brief and informal and attempt to put you in the right frame of mind for the next few chapters.


   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net