C#: A language alternative or just J--?, Part 2

The semantic differences and design choices between C# and Java

1 2 3 Page 2
Page 2 of 3
TextBlock tb;
public class TextBlock {
   private Color _bgColor;
   public Color getBackgroundColor() {
      return _bgColor;
   }
   public Color setBackgroundColor(Color value_) {
      _bgColor = value_;
   }
   // ...etc...
}
TextBlock tb;
if (tb.getBackgroundColor() == Color.green) {
   tb.setBackgroundColor(Color.red);
} else {
   tb.setBackgroundColor(Color.blue);
}

The meaning of the preceding example code in Java and C# is identical, but the syntax in C# is cleaner.

You may have heard that property accessors are simply methods used for accessing objects' internal variables. This idea is a complete misunderstanding of what property accessors truly are. While property accessors often, even usually, simply set and return the value of a private variable, property accessors are most useful when they return a calculated or deferred value. What's more, a property accessor that today returns the value of an internal variable might tomorrow return a value it got from somewhere else, if the class's implementation changed. The classes that access the property don't need to know where the property value came from, as they would if they were accessing an internal variable. Property accessors cannot break encapsulation in the way that public field accessors do. They are simply not the same thing. Whether defined by convention as in Java or built into a language like C#, properties are an excellent way to improve decoupling between classes.

Delegates

Delegates are C#'s answer to C++ function pointers, except that delegates are safe, and function pointers are dangerous. Both Java and C# have done away with function pointers, finding safer ways to maintain references to behavior that is determined at runtime (which is what function pointers are used for in C++).

C# borrowed the concept of delegates from Delphi and Visual J++. A delegate is a reference to a method, plus either a class or an instance against which the method can be called, depending on whether or not the method is static. Delegates are useful whenever behavior needs to be deferred to another object. For example, a sort algorithm might take a delegate as one of its arguments:

// Declaration of the delegate
delegate int Comparator(object a, object b);
class Quicksort {
   static void sort(Comparator c, object[] 
   objectsToSort) {
      // ... quicksort logic leading to a
comparison
         if (c(objectsToSort[left], 
         objectsToSort[pivot]) > 0) {
            // recursive call...
         } else { // ...and so on...
   } };

In the preceding code you see the delegate called Comparator declared and then used as an argument in Quicksort.sort(), which is static. In the "if" statement, the delegate c is called just as if it were a method. This is equivalent to calling through a function pointer in C, except the delegate is type-safe and can't possibly have an illegal value.

The typical Java approach usually involves implementing an interface, like this:

public interface Comparator {
   int compare(Object a, Object b);
}
public class Quicksort  {
   public static void sort(Comparator c, Object[] 
   objectsToSort) {
         // ...quicksort logic...
         if (c.compare(objectsToSort[left], 
         objectsToSort[pivot]) > 0) {
         // ... and so on
      }
   }
};

The object implementing Comparator in the preceding code will commonly be in an inner or even anonymous class. Again, as in the case of properties, the results in Java and C# are quite similar, but the C# syntax is slightly clearer. Delegates are more than just syntactic shorthand for interface calls, however. A delegate can maintain a reference both to a method and to an instance upon which that method can be invoked. You'll see an example of this usage in the next section on events. Also, you'll find a link to a Sun whitepaper on delegates in Resources.

Events and event notification

C# events operate much like Java 1.1+ events, except that C#'s are integrated into the language. For a class to receive an event, it must have a field or property that is a delegate marked with the event keyword. From inside the class, this delegate member is just like any other delegate: it can be called, checked to see if it's null, and so forth. From outside the class, however, there are only two things you can do with it: add or remove a method to the delegate, using the operator+= or the operator-=, respectively. Event delegates apparently act as collections or lists of references to methods and instances.

In essence, an event delegate is a list of methods to be called when the delegate is invoked. In my opinion, this usage is extremely confusing, since in this particular case delegates act like a collection of method pointers, rather than a single method pointer. The language spec does, however, specify that all delegates define addition and subtraction operators, implying that any delegate may be used in this way.

For example, here is the definition of part of a class called MyButton, which can respond to mouse clicks:

public delegate void ClickHandler(object source, 
Event e);
public class MyButton: Control {
   public event ClickHandler HandlerList;
   protected void OnClick(Event e) {
      if (HandlerList != null) {
         HandlerList(this, e);
      }
   }
   //... override other methods of Control...
}

A dialog class containing one of these buttons could then register itself to receive clicks from the button, adding one of its methods to the button's HandlerList with the operator+=, like so:

public class OneWayDialog: Control {
   protected MyButton DialPhoneButton;
   public OneWayDialog() {
      DialPhoneButton = new MyButton("Dial Phone");
      DialPhoneButton.HandlerList += new 
      ClickHandler(DoDial);
   }
   public void DoDial(object source, Event e) {
      // Call method that dials phone
number
   }
}

When a OneWayDialog is created, it creates a MyButton and then adds a delegate to its method DoDial() to the MyButton's HandlerList. When the user clicks the MyButton, the framework calls the MyButton's OnClick() method, which then invokes the HandlerList delegate, resulting in a callback to OneWayDialog.DoDial() against the original OneWayDialog instance.

In Java, this sort of mechanism (called event multicasting) is handled with lists of objects that implement event listener interfaces. Manipulation of these lists of interfaces must be done manually, although there are "support" classes that do it for you. C# manages these "listener lists" under the hood by building them into the delegate's semantics. While writing code that maintains listener lists in Java can be a bit tedious, at least it's clear what's going on. C# "simplifies" the handling of these lists by building it into the language, but at the cost of creating a language feature (the delegate) that behaves like a method pointer sometimes and like a collection of method pointers at other times. This may just be a matter of taste, but personally I think the delegates' semantics are messy and confusing. On the other hand, not everyone fully understands Java event listener interfaces the first time.

Operator overloading

Operator overloading lets you define how operators can be used with their classes. Operations on instances of those classes can then be expressed in expression notation using the operators, rather than as explicit method calls. Operators used in expressions map directly to the class's operator overloading definitions, subject to the language's rules for operator precedence and associativity. Mathematics packages often use operator overloading to provide a more intuitive programming model for their objects. For example, instead of matrixA.matMul(matrixB), the operator called operator* can be overloaded to handle matrix multiply,

yielding matrixA = matrixA * matrixB;.

Unlike Java, C# permits operator overloading, using a syntax almost identical to that of C++. Some operators that can be overloaded in C++, such as operator=, cannot be overloaded in C#. Experienced C++ programmers will have to learn and remember the differing rules for C# operator overloading, despite the almost identical syntax.

In light of their experience with C++, Java's designers decided deliberately to leave operator overloading out of Java. The question of operator overloading in Java is an ongoing debate. Fans of operator overloading claim that leaving it out of the language makes some code unreadable, if not virtually unwritable. Many in the numerics community would like to see operator overloading added to Java. Overloading detractors say that operator overloading is widely abused and that it all too often makes code unmaintainable. For the time being, the free program JFront (whose name is a pun on CFront) provides a preprocessor that implements operator overloading for Java (see Resources).

Methods

C# methods have some features that Java does not. In particular, C# provides several modifiers on method parameters and has keywords for virtual methods and method overriding.

As in C, C# method parameters are value parameters. The ref modifier on a method parameter makes that parameter a reference, so any change to its values is reflected in the caller's variable. The out modifier indicates that the parameter is an output parameter, which is identical to a reference parameter, except that the parameter must be assigned definitely before the method returns. The params keyword allows variable-length argument lists. For example, a method to print an arbitrary number of items might look like this:

#import main
void PrintItems(params object[] values) {
   foreach (object value in values) {
      Console.WriteLine(value);
   }
}

C# methods are, by default, nonvirtual but can be made virtual by explicit use of the keyword virtual. The C# virtual keyword in a base class defines a method as virtual. Subclasses must use the override modifier to override inherited virtual methods. Without the override modifier, a method in a subclass with the same signature as an inherited virtual method simply hides the superclass implementation, although the compiler produces a warning in this case. The keyword new, used as a method modifier, hides the superclass implementation of an inherited virtual method and turns off the error message.

C#'s designers went to a great deal of trouble to create fine-grained control over method overriding and hiding, which interacts in various subtle ways with accessibility (public, private, internal, and so on). In this case, C# is actually more complex than C++. The ref and out method parameter modifiers are an old, useful idea, and even more useful in a network environment. Network object marshaling code can use them to decide which objects to serialize and which to ignore.

Preprocessor

The C preprocessor was not included in Java because it was seen as an invitation to poor coding practice and an enormous source of bugs. C# includes a preprocessor essentially identical to that of C. Several preprocessors for Java are freely available for programmers addicted to this feature.

Native 'unsafe' code

One of C#'s most widely touted features is its direct integration of native code. Code blocks bearing the modifier unsafe can use C#'s pointer types to access unmanaged memory and platform-native resources. There is also a fixed keyword, used to "pin" an object to a specific address and protect it from the garbage collector. The word unsafe indicates to the compiler that the code being called uses memory pointers. While the C# specification targets performance as the reason for using unsafe code, it will probably be used more commonly for accessing the Win32 API and existing DLLs. The Java Native Interface (JNI) allows access to underlying system resources, but the Java language itself does not. JNI provides access through a software layer and in another language, usually C.

By this point, you have a good basic understanding of many C# features, which make it interesting and useful to Windows programmers. But the question remains: Why create this new language? Why not just integrate Java more closely with Windows or, even better, integrate Windows with Java? The next section addresses those questions.

The Standard Open Proprietary architecture

Microsoft has submitted C# to ECMA (European Computer Manufacturers Association), the European standards body that standardized ECMAScript. This is probably a thumb in the eye to Sun, which scrubbed Java standardization plans with both ECMA and ISO last year because of copyright concerns. (It remains to be seen how genuinely and successfully Microsoft will push for C# standardization.) These so-called standardizations of what are essentially proprietary technologies muddy the water for people doing real work in standardization development. True open standards are created in an open forum, with multiple parties providing input as the technology develops. Every high-tech company would like to be in the position of controlling an open proprietary architecture. To paraphrase Nixon aide Chuck Colson, if you've got customers by the architecture, their hearts and minds will follow.

Standards organizations should drive a hard bargain with companies who want to develop proprietary technologies behind closed doors and then come begging for "standardization" long after the time for meaningful community input has passed. Those that fail to do so risk becoming irrelevant repositories for vested interests instead of advocates for open systems and collaborative development. On the other hand, vendors can hardly be expected to contribute heavily to the success of their competitors' infrastructures. Consequently, standards organizations should accept proprietary technologies into their processes only if the originators of those technologies are willing to relinquish veto power over future development.

1 2 3 Page 2
Page 2 of 3