CSE341 Notes for Friday, 6/5/09

I spent some time discussing the upcoming final exam. Then I gave a short introduction to C#.

We discussed the history of C#. Many people believe it is a proprietary language, but actually Java is the proprietary language controlled by Sun. C# is controlled by an international standards organization called ecma. Even so, there has always been resistance to C# because of its association with Microsoft.

It is still the case that most C# programming is done for the .NET Framework. But you can get a free implementation of C# for linux and Macs from the mono project. I had downloaded and installed mono on my Mac the night before and it worked just fine.

Of course I had to begin with the standard "hello world" program:

        using System;

        public class Hello {
            public static void Main() {
                Console.WriteLine("hello world");
            }
        }
This is very similar to Java. The convention in C# is to capitalize method names, so it's "Main" instead of "main". Also, C# allows you to omit the "String[] args" if you don't need it (and it would be "string[] args" in C#, with a lower-case "s"). And instead of calling System.out.println, we call Console.WriteLine. But otherwise this looks a lot like Java.

I said that my best analogy is that this is like the difference between American English and British English. If you visit London, you'd certainly understand the words being spoken, but you wouldn't understand all of the cultural references. So if you went to learn C#, you'd find that you understand a lot of the core syntax and concepts already, but you have to learn the differences between the two frameworks.

I wrote a paper about this in 2002 called Can C# Replace Java in CS1 and CS2?. I had a hard time getting the paper accepted for publication. Many reviewers said that nobody would be interested in this question and that they didn't want to do anything to help out Microsoft.

I basically made two main points about C# versus Java. First, I said that because C# came later, it was able to improve on many of the flaws in Java. In fact, many of the improvements that I mention in my 2002 paper were added to Java starting with version 1.5. So while there is some truth to the idea that Microsoft copied many things Sun had done, it's also true that Sun then copied many things Microsoft had done. And there was a kind of space race to see who get manage to get generics done first. C# won that contest as well and the generics were done much better in C# than in Java. Java's implementation of generics depends on what is known as type erasure. You can't, for example, request a generic type at runtime that wasn't mentioned prior to runtime. In C# you can because the virtual machine understands generic definitions.

The second point I made is that C# has continued to evolve at a much faster pace than Java. Perhaps evolve is the wrong word, because C# seems to be suffering from the "kitchen sink" mentality of adding more and more features. As a result, C# has become a much more complex language than Java. That means that programmers are more likely to have their favorite features, but it also means that the language is more difficult to learn than Java.

The next example I showed involved a Point class with properties. Properties have been part of C# from the beginning. They are similar to Ruby attributes in that you don't need to use a method call syntax. For example, with a Java Point class that is well encapsulated, you'd increment a Point object's x field by saying:

        p.setX(p.getX() + 1);
In C# you can simply say:

        p.X++;
The C# compiler will translate this line of code into the appropriate calls on the get and set methods. So this is easier for a programmer to use, but it also tends to obscure the actual method calls that are taking place.

Below is the code for the Point example:

        using System;
        using System.Collections;
        
        public struct Point : IComparable<Point> {
            private int x;
            private int y;
        
            public Point(int x, int y) {
                this.x = x;
                this.y = y;
            }
        
            public override String ToString() {
                return "(" + x + ", " + y + ")";
            }
        
            public int CompareTo(Point other) {
                double d = Math.Sqrt((x - other.x) * (x - other.x) +
                                     (y - other.y) * (y - other.y));
                if (d < 0)
                    return -1;
                else if (d == 0)
                    return 0;
                else // d > 0
                    return 1;
            }
        
            public int X {
                get { return x; }
                set { x = value; }
            }
        
            public int Y {
                get { return y; }
                set { y = value; }
            }
        
            public static Point operator+(Point p1, Point p2) {
                return new Point(p1.x + p2.x, p1.y + p2.y);
            }
        }
        
        public class Example {
            public static void Main() {
                Point p1 = new Point();
                Console.WriteLine("p1 = " + p1);
                int sum = p1.X + p1.Y;
                p1.X = 3;
                p1.X++;
                p1.Y = 19;
                Console.WriteLine("p1 = " + p1);
                Point p2 = new Point(7, 8);
                Point p3 = p1 + p2;
                Console.WriteLine("p2 = " + p2);
                Console.WriteLine("p3 = " + p3);
        
                Point[] points = {new Point(3, 4), new Point(8, 2), new Point(0, 5),
                                  new Point(2, 0), new Point(2, 2)};
                Array.Sort(points);
                Console.WriteLine(points);
                foreach (Point p in points)
                    Console.WriteLine(p);
            }
        }
Other than the X and Y properties, this is very similar to how we would write the class in Java.

The next example I showed involved code for counting words in a file. It also looks a lot like Java, although we can use the very convenient ReadToEnd method that reads an entire file into a single string and we were able to use an array-style bracket notation for the Map (which .NET calls a Dictionary):

        using System;
        using System.IO;
        using System.Collections;
        using System.Text.RegularExpressions;
        using System.Collections.Generic;
        
        public class WordCount {
            public static void Main() {
                Console.Write("Enter the name of the file to process: ");
                StreamReader input = new StreamReader(Console.ReadLine());
        
                Dictionary count = new Dictionary();
                foreach (string s  in Regex.Split(input.ReadToEnd(), @"[^a-zA-Z']+")) {
                    String s2 = s.ToLower();
                    if (!count.ContainsKey(s2))
                        count[s2] = 1;
                    else
                        count[s2]++;
                }
                Console.WriteLine("length = " + count.Count);
                foreach (string s in count.Keys)
                    if (count[s] >= 500)
                        Console.WriteLine(count[s] + "  " + s);
            }
        }
Then I showed an example that involves operator overloading and varargs (something that was added to Java only after C# came out).

        using System;
        using System.Collections;
        using System.Collections.Generic;
        
        public class SetTest {
            public static void Main() {
                Set s1 = new Set(3, 6, 9, 12, 15);
                Set s2 = new Set(2, 4, 6, 8, 10, 12);
                Console.WriteLine("s1 = " + s1);
                Console.WriteLine("s2 = " + s2);
        
                Set s3 = s1 + s2;
                Set s4 = s1 * s2;
                Console.WriteLine("s3 = " + s3);
                Console.WriteLine("s4 = " + s4);
                Console.WriteLine();
        
                foreach(int n in s1)
                    Console.WriteLine(n);
                Console.WriteLine();
        
                for (int i = 0; i < s2.Count; i++) {
                    Console.WriteLine(s2[i]);
                }
            }
        }
        
        class Set : IEnumerable {
            private List<int> data;
        
            public Set(params int[] data) {
                this.data = new List<int>(data);
                this.data.Sort();
            }
        
            public static Set operator +(Set s1, Set s2) {
                Set result = new Set();
                foreach (int n in s1.data)
                    result.data.Add(n);
                foreach (int n in s2.data)
                    if (!result.data.Contains(n))
                        result.data.Add(n);
                result.data.Sort();
                return result;
            }
        
            public static Set operator *(Set s1, Set s2) {
                Set result = new Set();
                foreach (int n in s1.data)
                    if (s2.data.Contains(n))
                        result.data.Add(n);
                return result;
            }
        
            public int this[int i] {
                get {return data[i]; }
            }
        
            public int Count {
                get {return data.Count; }
            }
        
            public override string ToString() {
                if (data.Count == 0)
                    return "[]";
                else {
                    string result = "[" + data[0];
                    for (int i = 1; i < data.Count; i++)
                        result += ", " + data[i];
                    return result + "]";
                }
            }
        
            public IEnumerator GetEnumerator() {
                foreach (int n in data)
                    yield return n;
            }
        }
The interesting elements of this example are the fact that we can overload the plus and times operator to say (s1 + s2) and (s1 * s2). These do set union and set intersection. We also overloaded the square brackets to allow clients to refer to s[i] for the ith element. And we can use the foreach loop with a set because we defined a method called GetEnumerator. Notice that it was simple to write this method using the "yield" statement in C#. This isn't as powerful as the yield statement in Ruby, but it can be used to define an iterator like this.

Then we looked at an example that involves reading numbers from the console, which is fairly simple in C# using ReadLine and int.parse, and having a true swap method that swaps two values by using reference parameters, which are not available in Java:

        using System;
        
        public class Swap {
            public static void Main() {
                Console.Write("int 1? ");
                int n1 = int.Parse(Console.ReadLine());
                Console.Write("int 1? ");
                int n2 = int.Parse(Console.ReadLine());
        
                Console.WriteLine("initially n1 = " + n1 + ", n2 = " + n2);
                swap(ref n1, ref n2);
                Console.WriteLine("after swap n1 = " + n1 + ", n2 = " + n2);
            }
        
            public static void swap(ref int x, ref int y) {
                int temp = x;
                x = y;
                y = temp;
            }
        }
Finally, I showed an example that involves a nullable type. Because primitive values like ints and doubles are not objects, they can't be set to null. But in database programs and in spreadsheet programs like Excel, it is common to have a numeric quantity be null. For example, if you ask for the average of a sequence of values, you will get different answers if you have some fields set to null (or spreadsheet cells empty) versus having them set to 0. C# added this feature to make it easier to interoperate with database programs. In C#, you add a question mark at the end of the name of a value type to turn it into a nullable type. The program below uses an int? variable (a nullable int):

        using System;
        
        public class Hello {
            public static void Main() {
                int? n = null;
                Console.WriteLine("n = " + n);
                n = 38;
                Console.WriteLine("n = " + n);
            }
        }
For those who are interested, I encourage you to download some or all of these programs and try running them yourself. On a Windows machine you can go into Visual Studio to do so, but there are also command line versions of the C# compiler. On Windows, I use TextPad to connect me to the C# runtime compiler, which is known as "csc.exe". On my Mac, I installed Mono, created my program files in my favorite editor (emacs), and then gave commands like the following in a terminal window:

        gmcs Hello.cs
        mono Hello.exe
Below is the complete list of programs:


Stuart Reges
Last modified: Sun Jun 7 13:27:18 PDT 2009