Value types
Value types represent primitive values such as integers or
floating-point values. There are three general categories of value types:
enumerations, which are described in detail in Chapter 10; built-in value
types; and user-defined value types, or structs. As we have seen, value types
store their data directly on the stack. Since parameters are by default passed
by value in C#, when a value-type parameter is passed into a method, a new copy
of the value is created; any changes made to the parameter won't result in
changes to the variable passed into the method. Method parameters are discussed
in detail in Chapter 12.
Built-in value types
The .NET Framework provides a number of
predefined value types. These represent various integer, floating point,
character, and Boolean values. In other programming languages these are often
referred to as primitive data types. The built-in data types of the .NET
Framework are defined as structures and can be found in the System namespace. A list of the
built-in value types available in C# is given below:
|
Struct Name
|
C# Alias
|
IL Alias
|
CLS-
Compliant
|
Description
|
Range
|
|
Boolean
|
bool
|
bool
|
Yes
|
Boolean value
|
true or false
|
|
Byte
|
byte
|
unsigned
int8
|
Yes
|
8-bit unsigned integer
|
0 to 255
|
|
Char
|
char
|
char
|
Yes
|
16-bit Unicode character
|
|
|
Decimal
|
decimal
|
(Not an IL primitive type)
|
Yes
|
128-bit high precision
|
±1.0E-28 to ±7.9E+28 (and 0.0) decimal notation
|
|
Double
|
double
|
float64
|
Yes
|
64-bit double precision floating point
|
±5.0E-324 to ±1.7E+308 (and 0.0) floating point number
|
|
Int16
|
short
|
int16
|
Yes
|
16-bit signed integer
|
-32768 to 32767
|
|
Int32
|
int
|
int32
|
Yes
|
32-bit signed integer
|
-2,147,483,648 to 2,147,483,647 (-2.15E+9 to 2.15E+9)
|
|
Int64
|
long
|
int64
|
Yes
|
64-bit signed integer
|
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (-9.22E+18
to 9.22E+18)
|
|
SByte
|
sbyte
|
int8
|
No
|
8-bit signed integer
|
-128 to 127
|
|
Single
|
float
|
float32
|
Yes
|
32-bit single precision floating point
|
±1.5E-45 to ±3.4E+38 (and 0.0) floating point number
|
|
UInt16
|
ushort
|
unsigned
int16
|
No
|
16-bit unsigned integer
|
0 to 65535
|
|
UInt32
|
uint
|
unsigned
int32
|
No
|
32-bit unsigned integer
|
0 to 42,949,667,295 (0 to 4.29E+9)
|
|
UInt64
|
ulong
|
unsigned
int64
|
No
|
64-bit unsigned integer
|
0 to 18,446,744,073,709,551,615 (0 to 1.84E+19)
|
|
IntPtr
|
|
native
int
|
Yes
|
Signed integer of a platform-specific size (for example 32-bit on a
32-bit system)
|
(Platform-specific)
|
|
UIntPtr
|
|
native
unsigned int
|
No
|
Unsigned integer of a platform-specific size
|
(Platform-specific)
|
|
Typed
Reference
|
|
typedref
|
No
|
Pointer to a memory location where data is stored, together with a
representation of the type stored at that location
|
|
Notice that decimal is treated here as a
primitive, since it has a C# alias, although it is actually a composite type
when compiled to IL. The last three types (IntPtr, UIntPtr,
and TypedReference)
are primitive types in IL, but don't have C# aliases, and can't be instantiated
using a normal assignment; instead we need to use the types' constructors or
other keywords.
Platform-specific integers
The two platform-specific integral types, IntPtr
and UIntPtr,
are used for native resources such as window handles. The size of such
resources will vary according to the hardware and operating system in use, but
will always be big enough to hold a pointer on the system (hence the name). For
example, to retrieve the handle for a Windows Form we can use:
IntPtr hWnd = this.Handle;
Note that IntPtr is CLS-compliant, but the
unsigned equivalent, UIntPtr, is not.
Typed references
The last type TypedReference is by far the least
common, and its use is poorly documented, so we'll take a moment to look at it.
It represents data by containing both a reference to the location in memory where the data is stored, and
a run time representation of the type of this data. Only local variables and
parameters may be of the TypedReference type fields cannot
be typed references. The TypedReference type is not
CLS-compliant.
There are a number of undocumented C# keywords (completely
absent from the C# language specification) which we can use to create and
manipulate variables of this type. We can create a typed reference from a variable using the __makeref
keyword:
int i = 32;
TypedReference tr = __makeref(x);
The original type of the variable represented by the typed reference can be found using the
__reftype keyword:
Finally, the value can be extracted from the TypedReference
using the __refvalue
keyword:
int j = __refvalue(tr,
int);
Typed references can be used to represent method arguments
in variable argument lists (varargs). Such argument lists can be passed into methods and accessed with the (similarly
undocumented) __arglist
keyword:
// Method with a variable argument list
// We use the __arglist
keyword to represent this
public static void PrintToConsole(__arglist)
{
//
Create a System.ArgIterator object to loop through the args
ArgIterator ai = new ArgIterator(__arglist);
//
Each item in the ArgIterator is a TypedReference,
//
and we can convert them to objects using the
//
TypedReference.ToObject() static method
while (ai.GetRemainingCount() > 0)
{
TypedReference tr = ai.GetNextArg();
Console.WriteLine(TypedReference.ToObject(tr));
}
}
// Main method from which we call our
varargs method
public static void Main()
{
//
Define some parameters with different types
int
x = 23;
string y = "a string";
double z = 19.25;
//
Call our variable argument method, passing
//
the arguments in, again using the __arglist
keyword
PrintToConsole(__arglist(x,
y, z));
}
Note that, because
this code uses undocumented keywords (which aren't guaranteed to work in future
versions of C#), it isn't generally advisable to take this approach for a
cleaner way of doing this, see the section on the params
keyword in Chapter 12.