A First C# Program: 'Hello World'
Let's begin in the traditional way, by looking at the code of a Hello World program (note that the tabulation and line numbers are included just for the sake of readability).
1. using System;
2. public class HelloWorld
3. {
4. public static void Main()
5. {
6. // This is a single line comment
7. /* This is a
8. multiple
9. line comment */
10. Console.WriteLine("Hello World! From Akshat");
11. }
12. }
The first thing to note about C# is that it is case-sensitive. You will therefore get compiler errors if, for instance, you write 'console' rather than 'Console'.
The second thing to note is that every statement finishes with a semicolon (;) or else takes a code block within curly braces.
As C# is an object-oriented language, C# programs must be placed in classes. Line 2 above declares the class to be named 'HelloWorld'.
Line 1 of the code declares we are using the System namespace. The point of this declaration is mostly to save ourselves time typing. Because the 'Console' object used in line 10 of the code actually belongs to the 'System' namespace, its fully qualified name is 'System.Console'. However, because in line 1 we declare that the code is using the System namespace, we can then leave off the 'System.' part of its name within the code.
When compiled and run, the program above will automatically run the 'Main' method declared and begun in line 4. Note again C#'s case-sensitivity - the method is 'Main' rather than 'main'.
Lines 6-9 of the program are ignored by the compiler, being comments entered by the programmer for his own benefit. Line 6 shows a single line comment, in which everything on the line after the two forward slashes is ignored by the compiler. Lines 7-9 demonstrate a multi-line comment, in which everything between the opening /* and closing */ is ignored, even when it spans multiple lines.
The statement on line 10 calls the 'WriteLine' method of the Console class in the System namespace. It should be obvious how this works in the given example - it just prints out the given string to the 'Console' (on PC machines this will be a DOS prompt).
In order to run it, the program above must first be saved in a file. Unlike in Java, the name of the class and the name of the file in which it is saved do not need to match up, although it does make things easier if you use this convention. In addition, you are free to choose any extension for the file, but it is usual to use the extension '.cs'.
Suppose that you have saved the file as 'HelloWorld.cs'. Then to compile the program from a command line, you would use the command
csc HelloWorld.cs
(for Visual Studio .NET users: compile by pressing Ctrl-Shift-B)
This command would generate the executable HelloWorld.exe, which could be run in the usual way, by entering its name:
HelloWorld
(for Visual Studio .NET users: run by pressing Ctrl-F5)
Fairly obviously, this program would produce the output:
Hello World! From Akshat.
Variable
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 refered to by v.
Value Types
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 optimised 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.
C# Type | .Net Framework (System) type | Signed? | Bytes Occupied | Possible Values |
sbyte | System.Sbyte | Yes | 1 | -128 to 127 |
short | System.Int16 | Yes | 2 | -32768 to 32767 |
int | System.Int32 | Yes | 4 | -2147483648 to 2147483647 |
long | System.Int64 | Yes | 8 | -9223372036854775808 to 9223372036854775807 |
byte | System.Byte | No | 1 | 0 to 255 |
ushort | System.Uint16 | No | 2 | 0 to 65535 |
uint | System.UInt32 | No | 4 | 0 to 4294967295 |
ulong | System.Uint64 | No | 8 | 0 to 18446744073709551615 |
float | System.Single | Yes | 4 | Approximately ±1.5 x 10-45 to ±3.4 x 1038 with 7 significant figures |
double | System.Double | Yes | 8 | Approximately ±5.0 x 10-324 to ±1.7 x 10308 with 15 or 16 significant figures |
decimal | System.Decimal | Yes | 12 | Approximately ±1.0 x 10-28 to ±7.9 x 1028 with 28 or 29 significant figures |
char | System.Char | N/A | 2 | Any Unicode character (16 bit) |
bool | System.Boolean | N/A | 1 / 2 | true or false |
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 - as we have mentioned above - is the ultimate base class of all other types. New reference types can be defined using 'class', 'interface', and 'delegate' declarations.
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').
object x = new object();
x.myValue = 10;
object y = x;
y.myValue = 20; // after this statement both x.myValue and y.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, eg
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
s1 = "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.
Escape Sequences and Verbatim Strings
When declaring a string variable, certain characters can't, for various reasons, be included in the usual way. C# supports two different solutions to this problem.
The first approach is to use 'escape sequences'. For example, suppose that we want to set variable a to the value:
"Hello World
How are you?"
We could declare this using the following command, which contains escape sequences for the quotation marks and the line break.
string a = "\"Hello World\nHow are you\"";
The following table gives a list of the escape sequences for the characters that can be escaped in this way:
Character | Escape Sequence |
' | \' |
" | \" |
\ | \\ |
Alert | \a |
Backspace | \b |
Form feed | \f |
New Line | \n |
Carriage Return | \r |
Horizontal Tab | \t |
Vertical Tab | \v |
A unicode character specified by its number e.g. \u200 | \u |
A unicode character specified by its hexidecimal code e.g. \xc8 | \x |
null | \0 (zero) |
The second approach is to use 'verbatim string' literals. These are defined by enclosing the required string in the characters @" and ". To illustrate this, to set the variable 'path' to the following value:
C:\My Documents\
we could either escape the back-slash characters
string path = "C:\\My Documents\\"
or use a verbatim string thus:
string path = @"C:\MyDocuments\"
Usefully, strings written using the verbatim string syntax can span multiple lines, and whitespace is preserved. The only character that needs escaping is the double-quote character, the escape sequence for which is two double-quotes together. For instance, suppose that you want to set the variable 'text' to the following value:
the word "big" contains three letters.
Using the verbatim string syntax, the command would look like this:
string text = @"the word ""big"" contains three letters."
Boxing
C# allows you convert any value type to a corresponding reference type, and to convert the resultant 'boxed' type back again. The following piece of code demonstrates boxing. When the second line executes, an object is initiated as the value of 'box', and the value held by i is copied across to this object. It is interesting to note that the runtime type of box is returned as the boxed value type; the 'is' operator thus returns the type of box below as 'int'.
int i = 123;
object box = i;
if (box is int)
{Console.Write("Box contains an int");} // this line is printed
Arrays
Single-Dimensional Arrays
The type of each array declared is given firstly by the type of basic elements it can hold, and secondly by the number of dimensions it has. Single-dimensional arrays have a single dimension (ie, are of rank 1). They are declared using square brackets, eg:
int[] i = new int[100];
This line of code declares variable i to be an integer array of size 100. It contains space for 100 integer elements, ranging from i[0] to i[99].
To populate an array one can simply specify values for each element, as in the following code:
int[] i = new int[2];
i[0] = 1;
i[1] = 2;
One can also run together the array declaration with the assignment of values to elements using
int[] i = new int[] {1,2};
or the even shorter version of this:
int[] i = {1,2};
By default, as we have seen, all arrays start with their lower bound as 0 (and we would recommend that you stick with this default). However, using the .NET framework's System.Array class it is possible to create and manipulate arrays with an alternative initial lower bound.
The (read-only) Length property of an array holds the total number of its elements across all of its dimensions. As single-dimensional arrays have just one dimension, this property will hold the length of the single dimension. For instance, given the definition of array i above, i.Length is 2.
Rectangular Arrays
C# supports two types of multidimensional arrays: rectangular and jagged. A rectangular array is a single array with more than one dimension, with the dimensions' sizes fixed in the array's declaration. The following code creates a 2 by 3 multi-dimensional array:
int[,] squareArray = new int[2,3];
As with single-dimensional arrays, rectangular arrays can be filled at the time they are declared. For instance, the code
int[,] squareArray = {{1, 2, 3}, {4, 5, 6}};
creates a 2 by 3 array with the given values. It is, of course, important that the given values do fill out exactly a rectangular array.
The System.Array class includes a number of methods for determining the size and bounds of arrays. These include the methods GetUpperBound(int i) and GetLowerBound(int i), which return, respectively, the upper and lower subscripts of dimension i of the array (note that i is zero based, so the first array is actually array 0).
For instance, since the length of the second dimension of squareArray is 3, the expression
squareArray.GetLowerBound(1)
returns 0, and the expression
squareArray.GetUpperBound(1)
returns 2.
System.Array also includes the method GetLength(int i), which returns the number of elements in the ith dimension (again, zero based).
The following piece of code loops through squareArray and writes out the value of its elements.
|
A foreach loop can also be used to access each of the elements of an array in turn, but using this construction one doesn't have the same control over the order in which the elements are accessed.
Jagged Arrays
Using jagged arrays, one can create multidimensional arrays with irregular dimensions. This flexibility derives from the fact that multidimensional arrays are implemented as arrays of arrays. The following piece of code demonstrates how one might declare an array made up of a group of 4 and a group of 6 elements:
int[][] jag = new int[2][];
jag[0] = new int [4];
jag[1] = new int [6];
The code reveals that each of jag[0] and jag[1] holds a reference to a single-dimensional int array. To illustrate how one accesses the integer elements: the term jag[0][1] provides access to the second element of the first group.
To initialise a jagged array whilst assigning values to its elements, one can use code like the following:
int[][] jag = new int[][] {new int[] {1, 2, 3, 4}, new int[] {5, 6, 7, 8, 9, 10}};
Be careful using methods like GetLowerBound, GetUpperBound, GetLength, etc. with jagged arrays. Since jagged arrays are constructed out of single-dimensional arrays, they shouldn't be treated as having multiple dimensions in the same way that rectangular arrays do.
To loop through all the elements of a jagged array one can use code like the following:
|
or
|
Enumerations
An enumeration is a special kind of value type limited to a restricted and unchangeable set of numerical values. By default, these numerical values are integers, but they can also be longs, bytes, etc. (any numerical value except char) as will be illustrated below.
When you define an enumeration you provide literals which are then used as constants for their corresponding values. The following code shows an example of such a definition:
|
Note, however, that there are no numerical values specified in the above. Instead, the numerical values are (we think) set up according to the following two rules:
1. For the first literal: if it is unassigned, set its value to 0.
2. For any other literal: if it is unassigned, then set its value to one greater than the value of the preceding literal.
From these two rules, it can be seen that DAYS.Monday will be set to 0, and the values increased until DAYS.Sunday is set to 6. Note also how we are referring to these values - the values specified in an enumeration are static, so we have to refer to them in code using the name of the enumeration: "DAYS.Monday" rather than just "Monday". Furthermore, these values are final - you can't change their runtime value.
The following code demonstrates how you can override the default setting which makes the default values integers. In this example, the enumeration values are set to bytes.
|
You can also override the default numerical values of any and all of the enumeration elements. In the following example, the first literal is set to value 1. The other literals are then set up according to the second rule given above, so DAYS.Sunday will end up equal to 7.
|
In the two examples given, the values of each literal has been unique within the enumeration. This is usually how you will want things to be, but in fact the values need not be unique. In the following case, the value of DAYS.Thursday is also set to equal 1. The values assigned to the other literals will follow the rules given previously, so both DAYS.Tuesday and DAYS.Friday will equal 2, etc.
|
A useful feature of enumerations is that one can retrieve the literal as a string from the numeric constant with which it is associated. In fact, this is given by the default ToString() method, so the following expression comes out as true:
DAYS.Monday.ToString()=="Monday"
The following code prints out both the literal and its constant value for the specified enumeration.
|
Since it's not immediately obvious what's going on in the main method here, let's take the time to go through it.
On line 9 we use the static GetValues method of the Enum class. When you pass this class an enumeration type - in this case, the type corresponding to EnumTest.DAYS - it returns an array of all the values of the elements within that enumeration. Note that the Enum class also has the GetNames method, which returns the literal strings.
On line 10 we set up a foreach loop, pulling out, into day, each value in the dayArray in turn. Note that this value is of type DAYS.
On line 11 we use string interpolation as part of the Console.WriteLine method. This method makes use of the String.Format method, so is equivalent to:
Console.WriteLine(String.Format("Number {1} of EnumTest.DAYS is {0}", day, day.ToString("d")));
And what the String.Format method does is to take 'textual representations' of the objects it is passed as parameters, and slots them into the appropriate places within the 'format string' it is passed. So this line of code is basically equivalent to:
Console.WriteLine("Number " + day.ToString("d").ToString() + " of EnumTest.DAYS is " + day.ToString());
Now, we've already noted that day.ToString() will return a literal string, but what about the method day.ToString("d")? Well, we had a stab at explaining this a while ago, but did very badly. In fact, we just made an error. So hopefully the following will be better.
The ToString method can take a single IFormatProvider parameter which indicates how the string conversion should be conducted. Values for this parameter can include things like "g", "d", "x", "f", etc. The stated implication of "d", however, is to render in 'Decimal format'. And when we use this on an enumeration member, it provides a string representation of the *numerical value* of the enumeration member. So, when we run the code above, what we get is the following output:
Number 0 of EnumTest.DAYS is Monday
Number 1 of EnumTest.DAYS is Tuesday
Number 2 of EnumTest.DAYS is Wednesday
Number 3 of EnumTest.DAYS is Thursday
Number 4 of EnumTest.DAYS is Friday
Number 5 of EnumTest.DAYS is Saturday
Number 6 of EnumTest.DAYS is Sunday
No comments:
Post a Comment