Because of the similarities to Java, we will concentrate on the differences and extensions C# has to offer.
class Hello { static void Main() { System.Console.WriteLine("Hello World"); } }
The C# version of our favorite program looks almost like the Java code. There are only few visible differences. The public qualifiers for the class and the method are missing (but they exist) and the method names start (following Microsoft's C++ convention) with a capital letter. What looks like the direct access to the attribute Console is in fact the call of the getter method of a property (more on this below). Otherwise, the program works just like its Java counterpart.
Another difference is the file organization. The name of the source file is independent of the classes contained in the file, and you can put as many classes in a file as you want.
Moving on to expressions and variables, there are not many differences either.
using System; class Test { static void Main() { double x = 1.5; Console.WriteLine("result={0}", 2+3*4); Console.WriteLine("x={0,-10}result={1:f5}", x, 2+3*4.0); Console.WriteLine("x**2={0,5}", Math.Pow(x, 2)); } }
result=14 x=1.5 result=14.00000 x**2= 2.25
The example demonstrates two more language features. The first one is the use of namespaces. C#'s hierarchical class organization follows the C++ namespace model rather than the package/module model we know from Java, Ada, and so forth. The using statement lets us refer to all symbols from the specified namespace without qualification (equivalent to Java's import XXX.*, which is usually not recommended).
The second feature is C#'s printf alternative (something all C programmers dearly miss when moving to Java). The WriteLine method can take a format string and a variable number of arguments. The format string follows more or less Java's MessageFormat. The placeholders are enclosed in curly braces and consist of the zero-based argument index, an optional size/alignment (negative number meaning left alignment), and an optional format specifier. The size/alignment is separated from the index with a comma, and the format specifier is preceded by a colon. In the example, the placeholder {0:f5} means that we want to format the second element of the variable argument list using the fixed-point format with five decimals.
As another welcome surprise for C/C++ programmers, C# also supports signed integer types such as uint (I must say that I have not missed them yet).
class Sample { static void Main() { uint i = 55; i -= 100; System.Console.WriteLine(i); } }
4294967251
Talking about build-in types, we do not want to forget the decimal type. Every (non-COBOL) programmer building financial applications has been faced with the question which type to use to represent monetary values. Since accountants hate rounding errors, using floating point arithmetic is not a good idea (already the conversion from a decimal string to the binary floating point representation may introduce some rounding error). As an example, we loose almost a cent (!) when adding a cent a million times to a billion dollars.
class Test { static void Main() { double x = 1e9, y = x, delta = 0.01; int n = 1000000; for (int i=0; i<n; i++) { y += delta; } double z = x + n * delta; System.Console.WriteLine("{0} {1} {2}", z, y, z - y); } }
1000010000 1000009999.99046 0.009536743164062
This does not happen when we switch to decimals.
class Test { static void Main() { decimal x = 1e9M, y = x, delta = 0.01M; int n = 1000000; for (int i=0; i<n; i++) { y += delta; } decimal z = x + n * delta; System.Console.WriteLine("{0} {1} {2}", z, y, z - y); } }
1000010000 1000010000 0
Note that decimal literal use the M (for money - or, equivalenty, Microsoft) prefix. Internally, the decimal type is a 128-bit structure consisting of a signed 96-bit integer mantissa and a (decimal) exponent between 0 and 28. The 96-bit mantissa gives us a decimal precision of about 28-29 digits.
Java knows two kinds of types: primitive types such as integers and doubles, and objects. Primitive types are predefined by the language, live on the stack, use value semantics, and have no methods. Objects on the other hand always live on the heap, have reference semantics and proper attributes and methods.
The .NET framework (the Common Language Runtime, CLR) approaches the type question in a different manner. All values are "objects", that is, they are all derived from System.Object, and can have attributes and methods. We can, for example, call the System.Object's ToString and GetType methods on all objects including number literals.
class Sample { static void Main() { int i = 55; System.Console.WriteLine(i.ToString()); System.Console.WriteLine(5.5.GetType().Name); } }
55 Double
But there are two kinds of types: value types and reference types. The value types live on the stack and have value semantics, the reference types live on the heap and have reference semantics. The built-in primitive types such as integers obviously belong to the value types, but they are not alone. We can define our own value types using structures. Here is a simple point structure consisting of two integers.
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return string.Format("({0}, {1})", this.x, this.y); } } class Sample { static void Main() { Point p = new Point(1, 2); Point q = p; q.x = 5; System.Console.WriteLine("p={0}, q={1}", p, q); } }
p=(1, 2), q=(5, 2)
If we change Point to a class, the two variables p and q become references pointing to the same object and the resulting output is:
p=(5, 2), q=(5, 2)
When using a value type in the context of a reference type (e.g., a collection, see Section 23.2.6>), the value has to be wrapped in an object just like in Java. However, this "boxing" is done automatically in C# (since version 1.5, Java has adopted this "autoboxing" syntax as well). Converting back to the value type is performed with a cast operation.
For immutable objects (objects that do not change their state after construction) such as strings it is hard to tell the difference between reference and value types. C#'s strings happen to be a reference types and all primitive types (such as integer) value types.
As a rule, value type should be small so that the copying implied by the value semantics does not cause any significant overhead. A good example of a non-primitive built-in value type is the decimal type.
Directly related to value versus reference type is the passing of arguments to functions (i.e., methods in C#). By default, arguments are passed by value. For a value type this actually means that the object is copied. Changing the argument therefore only alters the local copy.
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return string.Format("({0}, {1})", this.x, this.y); } } class Sample { static void Set(Point p) { p.x = 55; } static void Main() { Point p = new Point(1, 2); Set(p); System.Console.WriteLine("p={0}", p); } }
p=(1, 2)
For reference types, the reference is copied, but it points to the original object. Changing Point to a class lets us modify the point in the Set method.
class Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return string.Format("({0}, {1})", this.x, this.y); } } class Sample { static void Set(Point p) { p.x = 55; p = new Point(100, 200); } static void Main() { Point p = new Point(1, 2); Set(p); System.Console.WriteLine("p={0}", p); } }
p=(55, 2)
We can, however, deviate from the default argument handling and pass arguments explicitly by reference.
class Sample { static void Swap(ref int a, ref int b) { int tmp = a; a = b; b = tmp; } static void Main() { int i = 1, j = 2; Swap(ref i, ref j); System.Console.WriteLine("i={0}, j={1}", i, j); } }
i=2, j=1
The ref keyword is placed in front of the argument type as well as the actual argument.
When using ref, the variable passed as an argument must be initialized. Otherwise, the compiler issues an error. To implement multiple return values this can be prevented by using out instead of ref.
class Sample { static int Set(out int a) { a = 55; return 0; } static void Main() { int i; int rc = Set(out i); System.Console.WriteLine("i={0}, rc={1}", i, rc); } }
i=55, rc=0
Alltogether we are very close to the C/C++ with the exception of references to constant objects (passing by reference without the option to change the object).
All the familiar control statements work in C# just like they do in the other languages of the C family.
class Control { static int sign(int x) { if (x < 0) { return -1; } else if (x == 0) { return 0; } else { return 1; } } static void Main() { System.Console.WriteLine("sign(55)={0}", sign(55)); } }
Just like Java, C# insists on boolean expressions for the conditions.
The standard loops also work exactly as in Java. In particular, we can can define loop variables inside the for statement. The loop variable have to be of the same type and must not already be used in the current scope.
class Control { static void Main() { int i = 0; while (i < 3) { System.Console.WriteLine("i={0}", i); i++; } i = 0; do { System.Console.WriteLine("i={0}", i); i++; } while (i < 3); for (int j=0, k=1; j<3; j++, k += 2) { System.Console.WriteLine("j={0}, k={1}", j, k); } } }
Despite the similarities between Java and C#, we find some small but useful extension in almost every area of the language. With regards to control statements, the first one is the foreach loop which simplifies the iteration through a collection.
class Control { static void Main() { int[] numbers = { 1, 3, 5}; foreach (int i in numbers) { System.Console.WriteLine("i={0}", i); } } }
The little extension is so convenient that the Java language added it in version 1.5. In order not to upset the Java community by introducing a new keyword, Java replaced the "in" (which I personally use quite often for input streams) by a colon.
The second extension is the switch statement. In addition to integer expression, we can also use strings in the switch expression.
class Control { static void Main() { int i = 0; switch ("t" + "wo") { case "one": i = 1; break; case "two": i = 2; break; case "three": i = 3; break; } System.Console.WriteLine("i={0}", i); } }
The strings in the case clauses must be constants. This allows for an efficient implementation of the switch expression using "interned" strings. Basically, the string constants are stored in a hash table, and the implementation of the switch statement evaluates the string expression and tries to find it in the hash table. If it is not in the table, it is not interned and therefore does not match any of the case strings. If the string is found in the hash table, it can be efficiently compared to the case strings using the address of the interned strings.
The following definition of our person class again shows a lot of resemblance to the Java version, but also demonstrates the use of properties which goes beyond a naming convention as in Java.
public class Person { string name; int age; public Person(string name, int age) { this.name = name; this.age = age; } public string Name { get { return this.name; } } public int Age { get { return this.age; } set { System.Console.WriteLine("setting age to {0}", value); this.age = value; } } }
The class defines two properties, a read-only property for the name and a read-write property for the age of the person. By omitting all the signatures, C# keeps the syntax short and consistent. The argument passed to the setter is implicitly available as the value. We can now use the property just like an attribute, but behind the scenes the getter and setter methods will be called.
class Test { static void Main() { Person person = new Person("Homer", 55); person.Age = 66; System.Console.WriteLine("name={0}, age={1}", person.Name, person.Age); } } result: setting age to 66 name=Homer, age=66
With regards to inheritance, C# follows the C++ model more closely than Java. Methods, for example, do not use dynamic dispatching by default. Instead, you have to declare a method as virtual in the base class.
public class Person { string name; int age; public Person(string name, int age) { this.name = name; this.age = age; } public virtual string Hello() { return "Hello, I am " + this.name; } }
In the derived class, we have to tell the compiler explicitly that we are about to override a virtual method.
public class Employee : Person { private int number; public Employee(string name, int age, int number) : base(name, age) { this.number = number; } public override string Hello() { return "Hello, I am number " + this.number; } }
Also notice the C++ syntax with respect to the base class. Calling the constructor of the base class looks similar to C++ but with the name of the parent class replaced by base. At least the syntax is clearer than Java's call to super which looks like a normal method call, but has to be the first statement in a constructor. We can also call another constructor of the same class using this instead of base
public class Person { string name; int age; public Person(string name, int age) { this.name = name; this.age = age; } public Person(string name) : this(name, 0) {} ... }
From a client perspective, the code looks the same in Java and C#.
class Test { static void Main() { Person person = new Employee("Homer", 55, 1234); System.Console.WriteLine(person.Hello()); } } result: Hello, I am number 1234
Structures are the value type version of classes. While in C++ the user of a class decides whether to allocate the object on the stack or the heap, in C# the designer of a type makes this decision.
The most basic type of collection in C# is the array. The one-dimensional arrays work just like in Java. They are objects living on the heap which have to be created with the new statement. Once allocated, an array has a fixed size.
public class Test { public static void Main() { int[] numbers = new int[3]; for (int i=1; i<numbers.Length; i++) { numbers[i] = 10 + i; } foreach (int i in numbers) { System.Console.WriteLine("i=" + i); } } }
i=0 i=11 i=12
The elements of the array are set to the default value of the underlying type (here zero as the default value for integers). C# also supports the shortcut syntax when creating an array of specific values. As in Java, the allocation with new int[] can only be omitted in the initializer (when declaring the variable).
public class Test { public static void Main() { int[] numbers = {55, 66, 77}; foreach (int i in numbers) { System.Console.WriteLine("i=" + i); } numbers = new int[]{1, 2, 3}; foreach (int i in numbers) { System.Console.WriteLine("i=" + i); } } }
C# also has true multi-dimensional arrays (in contrast to arrays of arrays). The syntax is a straight-forward generalization of the one-dimensional case.
public class Test { public static void Main() { int[,] numbers = new int[3, 4]; for (int i=0; i<numbers.GetLength(0); i++) { for (int j=0; j<numbers.GetLength(1); j++) { numbers[i, j] = 10 * i + j; } } for (int i=0; i<numbers.GetLength(0); i++) { for (int j=0; j<numbers.GetLength(1); j++) { System.Console.Write("{0}\t", numbers[i, j]); } System.Console.WriteLine(); } } }
0 1 2 3 10 11 12 13 20 21 22 23
The standard arrays all use indexes starting at zero. It is possible to create arrays with other lower bounds using the static Array.CreateInstance method. The following example taking from [RICHTER02]> demonstrates its usage.
public class Test { public static void Main() { int[] lowerBounds = {1999, 1}; int[] lengths = {2, 4}; decimal[,] quarterlyRevenues = (decimal[,]) System.Array.CreateInstance(typeof(decimal), lengths, lowerBounds); int firstYear = quarterlyRevenues.GetLowerBound(0); int lastYear = quarterlyRevenues.GetUpperBound(0); decimal revenue = 100.0m; for (int year=firstYear; year<=lastYear; year++) { for (int quarter=1; quarter<=4; quarter++, revenue *= 1.1m) { quarterlyRevenues[year, quarter] = revenue; } } for (int year=firstYear; year<=lastYear; year++) { for (int quarter=1; quarter<=4; quarter++, revenue *= 1.1m) { System.Console.WriteLine( "year={0}, quarter={1}: {2:C}", year, quarter, quarterlyRevenues[year, quarter]); } } } }
year=1999, quarter=1: $100.00 year=1999, quarter=2: $110.00 year=1999, quarter=3: $121.00 year=1999, quarter=4: $133.10 year=2000, quarter=1: $146.41 year=2000, quarter=2: $161.05 year=2000, quarter=3: $177.16 year=2000, quarter=4: $194.87
Like Java and in contrast to C++ and Eiffel, the first version of the .NET framework (and therefore C#) does not support generic classes. Hence, C#'s collection classes are similar to Java and the dynamically typed languages (e.g., Smalltalk). Here is an example demonstrating the basic usage of lists and iterators (called enumerators in C#).
using System; using System.Collections; public class ListTest { public static void Main() { IList list = new ArrayList(); list.Add("blah"); list.Add("blub"); Console.WriteLine("list contains 'blah'? " + list.Contains("blah")); for (IEnumerator i=list.GetEnumerator(); i.MoveNext(); ) { string item = (string)i.Current; Console.WriteLine("item=" + item); } foreach (string item in list) { Console.WriteLine("item=" + item); } } }
The enumerator semantics also look like a compromise between Java and C++ iterators. An enumerator starts in front of its first element. Each call to MoveNext moves it forward to the next element or returns false if there is not element left. The Current property gives us access to the current element. There is also a Reset method which put the enumerator back to its initial position in front of the first element.
Note that we do not need to case when using the foreach loop. In interesting twist is added when storing value types in a collection. The collections always contain reference types, but the automatic boxing and simple unboxing make the difference almost invisible.
using System; using System.Collections; public class ListTest { public static void Main() { IList list = new ArrayList(); list.Add(55); list.Add(66); Console.WriteLine("list contains 55? " + list.Contains(55)); for (IEnumerator i=list.GetEnumerator(); i.MoveNext(); ) { int item = (int)i.Current; Console.WriteLine("item=" + item); } foreach (int item in list) { Console.WriteLine("item=" + item); } } }
Besides lists, C# supports the whole range of collections such as dictionaries, sets, stacks, and so forth. Here is an example using the hash table implementation of a dictionary.
using System; using System.Collections; public class DictionaryTest { public static void Main() { IDictionary map = new Hashtable(); map["blah"] = 55; map["blub"] = 66; foreach (string key in map.Keys) { Console.WriteLine("map[" + key + "]=" + map[key]); } for (IDictionaryEnumerator i=map.GetEnumerator(); i.MoveNext(); ) { Console.WriteLine("map[" + i.Key + "]=" + i.Value); } } }
A dictionary gives us access to its keys and values as collections which can be used conveniently in a foreach. With the dictionary enumerator it is also possible to iterate through keys and values at the same time.