C# is a type-safe language Variables· are declared as .being of a particular type; and each variable is constrained to hold only values of its declared type.
Variables can hold either value types or reference types, or they can be pointers. Here’s a quick recap of the difference between value types and reference types.
• Where a variable v contains a value type, it directly contains an object with some value. No other variable v’ can directly contain the object contained by v (although v’ might contain an object with the same value).
• Where a variable v contains a reference type, what it directly contains is something which refers to an object. Another variable v’ can contain a reference to the same object referred to by v.
We’ll be covering the following topics in this tutorial:
Value Types
C# defines the following value types:
- Primitives int i;
- Enum enum state {off, on }
- Struct struct Point{ int x, y; }
It is possible in C# to define your own value types by declaring enumerations or structs.
These user-defined types are mostly treated in exactly the same way as C#’s predefined value types, although compilers are optimized for the latter.
The following table lists, and gives information about, the predefined value types. Because in C# all· of the apparently fundamental value types are in fact built up from the (actually fundamental) object type; the list also indicates which System types in the .NET framework correspond to these pre-defined types.
In the following lines of code, two variables are declared and set with integer values.
Int X = 10
Int y = x
y = 20; // after this statement x holds value 10 and y holds value 20
Reference Types
The pre-defined reference types are object and string, where object – is the ultimate base class of all other types. New reference types can be defined using ‘class’, ‘interface’, and ‘delegate’ declarations. There fore the reference types are:
Predefined Reference Types .
• Object
• String
User Defined Reference Types
• Classes • Interfaces
• Delegates • Arrays
Reference types actually hold the value of a memory address occupied by the object they reference. Consider the following piece of code, in which two variables are given a reference to the same object (for the sake of the example, this object is taken to contain the numeric property ‘myValue’).
class Demo
{
class XYZ
{
public int myvalue;
}
public static void Maine)
{
Xyz X= new Xyz();
x.myvalue 10;
XYZ Z = x;
z.myvalue 20; II after this statement both
II x.myvalue and z.myvalue equal 20
}
}
This code illustrates how changing a property of an object using a particular reference to it is reflected in all other references to it. Note, however, that although strings are reference types, they work rather more like value types. When one string is set to the value of another, for example:
string S1 = “hello”;
string S2 = S1;
Then s2 does at this point reference the same string object as s1. However, when the value of s1 is changed, for instance with
sl = “goodbye”;
What happens is that a new string object is created for s1 to point to. Hence, following this piece of code, s1 equals “goodbye”, whereas s2 still equals “hello”.
The reason for this behavior is that string objects are ‘immutable’. That is, the properties of these objects can’t themselves change. So in order to change what a string variable references, a new string object must be created.
Pointers
This section gives a brief overview of pointers and their use in C#. It only scratches the surface of a complicated topic, however, so if you are new to pointers .it is recommended that you do further reading before using them in your code. Luckily, pointers are only-really needed in C# where execution speed is highly important.
A pointer is a variable that holds the memory address of another type. In C#, pointers can only be declared to hold the memory addresses of value types (except in the case of arrays – see below).
Pointers are declared implicitly, using the ‘reference’ symbol *, as in the following example:
Int *p;
This variation appears to work just as well as the previous one.
This declaration sets up a pointer ‘p’, which will point to the initial memory address of an integer (stored in four bytes).
The combined syntactical element *p (‘p’ prefixed by the referencer symbol ‘*’) is used to refer to the type located at the memory location held by p. Hence given its declaration, *p can appear in integer assignments like the following:
*p=5
This code gives the value 5 to the· integer that was initialized by the declaration. It is important, however, not to confuse such an assignment with one in which the derefencer symbol is absent, e.g.
P=5;
The effect of this assignment is to change the memory location held by p. It doesn’t change the value of the integer initialized by the original declaration; it just means that p no longer points to that integer. In fact, p will now point to the start of the four bytes present at memory location 5.
Another important symbol for using pointers is the operator &, which in this context returns the memory address of the variable it prefixes. To give an example of this symbol, the following code sets up p to point to integer is memory location:
int i = 5 ;
int ‘*P
p = &i;
Given the above, the code
*P=10
Changes the value of i to 10, since ‘*p’ can be read as ‘the integer located at the memory value held by p’.
There is another important piece of notation for pointers. Pointers can be declared for structs, as in ·the following example (which uses the ‘Coords’ struct defined further below):
coords x = new coords();
coords *y = &x;
One can then use the declared pointer y to access a public field of x (say z). This would be done using either the expression
(*y).z
Or .the equivalent expression, which uses the -> string:
y -> z
Note that. Some programmers place the referencer symbol immediately after the type name, for example:
Boxing
Boxing is an implicit conversion of a value type to the type object or to any interface type implemented by this value type. Boxing a value of a value allocated an object instance and copies the values into the new object.
Consider the following declaration of a value-type variable:
Int i = 123;
The following statement implicitly applies the boxing operation on the variables:
Object o = i;
The result of this statement is creating an object, on the stack, that references a value of the type int, on the heap. This value is a copy of the value-type value assigned to the variable i. The difference between the two variables, i and 0 is illustrated in the following figure.
It also possible, but never needed to perform the boxing explicitly as in the following example
Int i = 123;
Object O = (object) i;
Unboxing
Unboxing is an explicit conversion from the object to value type or from an interface type to a value type that implements the interface. An unboxing operation consists of:
• Checking the object instance to make sure it is a boxed value of the given value type.
• Copying the value from the instance into the value-type variable.
The following statements demonstrate both boxing and unboxing operations:
Int i = 123; // a value type
object O = i; //boxing
int j = (int) O; II unboxing
The following figure demonstrates the result of the preceding statements.
For the unboxing of value types to succeed at runtime, the item being unboxed must be a reference to an object that was previously created by boxing an instance of that value type. Attempting to unbox null or a reference to an incompatible value type will return an invaIidcastException.
OPERATORS
C# has a number of standard operators, taken from C, C++ and Java. Most of these should be quite familiar to programmers; the less common ones are covered elsewhere.
The diagram below lists the standard operators. Note that when writing classes it is possible to change the default behavior of some of these operators (i.e. to ‘overload’ the operator), although this should only be done where the resultant semantics makes sense. The table indicates which of the operators are overloadable.
Primary Operators