This chapter walks you through each aspect of the C# language. Many features of C# will be familiar if you have experience with a strongly typed object-oriented language.
Identifiers are names programmers choose for their types, methods, variables, and so on. An identifiermust be a whole word, essentially composed of Unicode characters starting with a letter or underscore. An identifier must not clash with a keyword. As a special case, the @ prefix can be used to avoid such a conflict, but the character isn’t considered part of the identifier that it precedes. For instance, the following two identifiers are equivalent:
Korn @Korn
C# identifiers are case-sensitive, but for compatibility with other languages, you should not differentiate public or protected identifiers by case alone.
A C# program is written by building new types and leveraging existing types, either those defined in the C# language itself or imported from other libraries. Each type contains a set of data and function members, which combine to form the modular units that are the key building blocks of a C# program.
Generally, you must create instances of a type to use that type. Those data members and function members that require a type to be instantiated are called instance members. Data members and function members that can be used on the type itself are called static members.
In this program, we build our own type called Counter
and another type called Test
that uses instances of the Counter
. The Counter
type uses the predefined type
int
, and the Test
type uses the static function member
WriteLine
of the Console
class defined in the System
namespace:
// Imports types from System namespace, such as Console using System; class Counter { // New types are typically classes or structs // --- Data members --- int value; // field of type int int scaleFactor; // field of type int // Constructor, used to initialize a type instance public Counter(int scaleFactor) { this.scaleFactor = scaleFactor; } // Method public void Inc( ) { value+=scaleFactor; } // Property public int Count { get {return value; } } } class Test { // Execution begins here static void Main( ) { // Create an instance of counter type Counter c = new Counter(5); c.Inc( ); c.Inc( ); Console.WriteLine(c.Count); // prints "10"; // Create another instance of counter type Counter d = new Counter(7); d.Inc( ); Console.WriteLine(d.Count); // prints "7"; } }
Each type has its own set of rules defining how it can be
converted to and from other types. Conversions between types may be
implicit or explicit. Implicit conversions can be
performed automatically, while explicit
conversions require a cast usingthe C cast
operator, ( )
:
int x = 123456; // int is a 4-byte integer long y = x; // implicit conversion to 8-byte integer short z =(short)x; // explicit conversion to 2-byte integer
The rationale behind implicit conversions is that they are guaranteed to succeed and not lose information. Conversely, an explicitconversion is required when runtime circumstances determine whether the conversion succeeds or when information might be lost during the conversion.
Most conversion rules are supplied by the language, such as the previous numeric conversions. Occasionally it is useful for developers to define their own implicit and explicit conversions (see Section 2.4 later in this chapter).
All C# types, including both predefined types and user-defined types, fall into one of three categories: value, reference, and pointer.
Value types typically represent basic types. Simple
types, such as basic numeric types (int
, long
, bool
, etc.) are structs, which are value
types. You can expand the set of simple types by defining your own
structs. In addition, C# allows you to define enums.
Reference types typically represent more complex types with rich functionality. The most fundamental C# reference type is the class, but special functionality is provided by the array, delegate, and interface types.
Pointer types fall outside mainstream C# usage and are used only for explicit memory manipulation in unsafe blocks (see Section 2.17 later in this chapter).
C# has two categories of predefined types:
Value types: integers, floating point numbers, decimal
, char
, bool
Reference types: object
,
string
Aliases for all these types can be found in the System
namespace. For example, the following
two statements are semantically identical:
int i = 5; System.Int32 i = 5;
The following table describes integral types:
sbyte
, short
, int
, and long
are signed integers; byte
, ushort
, uint
, and ulong
are unsigned integers.
For unsigned integers n bits wide, their possible values range from to 2n-1. For signed integers n bits wide, their possible values range from -2n-1 to 2n-1-1. Integer literals can use either decimal or hexadecimal notation:
int x = 5; ulong y = 0x1234AF; // prefix with 0x for hexadecimal
When an integral literal is valid for several possible integral types, the chosen default type goes in this order:
int uint long ulong.
These suffixes may be appended to a value to explicitly specify its type:
U
uint
or ulong
L
long
or ulong
UL
ulong
An implicit conversion between integral types is
permitted when the resulting type contains every possible value of
the type it is converted from. Otherwise, an explicit conversion
is required. For instance, you can implicitly convert an int
to a long
, but must explicitly convert an
int
to a short
:
int x = 123456; long y = x; // implicit, no information lost short z = (short)x; // explicit, truncates x
A float
can hold
values from approximately ±1.5 x 10-45 to
approximately ±3.4 x 1038 with seven
significant figures.
A double
can hold values
from approximately ±5.0 x 10-324 to
approximately ±1.7 x 10308 with 15 to 16
significant figures.
Floating-point types can hold the special values +0, -0, +∞,
-∞, or NaN (not a number) that represent the outcome of mathematical
operations such as division by zero. float
and double
implement the specification of the
IEEE 754 format types, supported by almost all processors, defined
at http://www.ieee.org.
Floating-point literals can use decimal or exponential notation. A
float
literal requires the suffix
“f” or “F”. A double
literal may
choose to add the suffix “d” or “D”.
float x = 9.81f; double y = 7E-02; // 0.07 double z = 1e2; // 100
An implicit conversion from a float
to a double
loses no information and is
permitted but not vice versa. An implicit conversion from an
sbyte
, short
, int
, or long
(or one of their unsigned
counterparts) to a float
or
double
is allowed for
readability:
short strength = 2; int offset = 3; float x = 9.53f * strength - offset;
If this example used larger values, precision might be lost.
However, the possible range of values isn’t truncated because the
lowest and highest possible values of a float
or double
exceed those of any integral
type. All other conversions between integral and floating-point
types must be explicit:
float x = 3.53f; int offset = (int)x;
The decimal
type can hold values from ±1.0 x
10-28 to approximately ±7.9 x
1028 with 28 to 29 significant
figures.
The decimal
type holds 28
digits and the position of the decimal point on those digits. Unlike
a floating-point value, it has more precision but a smaller range.
It is typically useful in financial calculations, in which the
combination of its high precision and the ability to store a
base10 number without rounding errors is
valuable. The number 0.1, for instance, is represented exactly with
a decimal
, but as a recurring
binary number with a floating-point type. There is no concept of +0,
-0, +∞, -∞, and NaN for a decimal.
A decimal
literal requires
the suffix “m” or “M”:
decimal x = 80603.454327m; // holds exact value
An implicit conversion from all integral types to a decimal
type is permitted because a decimal
type can represent every
possible integer value. A conversion from a decimal
to floating-point type or vice
versa requires an explicit conversion, since floating-point types
have a bigger range than a decimal
, and a decimal has more
precision than a floating-point type.
The char
type
represents a Unicode character.
A char
literal consists of either a character, Unicode
format, or escape character enclosed in single quote marks:
'A' // simple character 'u0041' // Unicode 'x0041' // unsigned short hexadecimal ' ' // escape sequence character
Table 2-1 summarizes the escape characters recognized by C#.