X
Business

Ten reasons to choose C#

For those interested in .NET programming, C# is a worthwhile addition to the programmer's toolkit. For those not interested in .NET programming, it's still a source of ideas.
Written by John Carroll, Contributor
COMMENTARY--C# is a language for writing .NET applications. It is not exclusive in this role (Visual Basic, C++, Java, COBOL, Python and Perl, among others, all have compilers that generate code that runs inside a .NET runtime), but it was created specifically to support features found in the .NET development platform.

It was designed by Anders Hejlsberg, famous for his creation of the Delphi programming language. It is standardized by the ECMA and on its way to standardization by the IEEE.

C# is a great language, and I have found it to have a number of advantages over Java the language (versus Java the development platform and runtime architecture, which was the subject of a previous article). I am NOT, however, going to say that these advantages necessarily make C# "better" than the Java language. Programming language preferences tend to be as varied as tastes in clothing. For those who prefer the Java syntax, a Java syntax compiler exists in the form of J#.

In other words, don't view my list of features as an attempt to convince you to swear off other programming languages. These are syntactically interesting features, not life-changing events. Most programmers who've spent a few years in the industry learn large numbers of languages. C# is just a particularly interesting addition to the pack.

1. foreach:
This is a simple, but powerful addition to a programming language, and something grabbed straight from the Visual Basic world. Operating in a similar fashion to a C-style "for" loop, it allows you to iterate over the contents of a collection without having to access elements in an array by index, as this example shows.

Example 1

The "foreach" operator relies on the collection object implementing the System.Collections.IEnumerable interface. This interface specifies a single method, GetEnumerator(), which returns an IEnumerator object.

IEnumerator is a simple iteration abstraction, with four methods that have to be implemented by any enumeration object. Fortunately, most collection objects already supply an enumerator, meaning users of standard collection objects of type ArrayList (for dynamic arrays) and Hashtable (for a map of objects using an arbitrary "key") can iterate through their contents using the IEnumerator implementation provided by these classes.

IEnumerable is a .NET construct to which C# maps, which means that any language can choose to support a foreach-style looping operand by mapping language features to IEnumerator retrieval and subsequent iteration. In fact, C# takes this implicit support a step further by allowing standard arrays to be used inside a foreach loop. Every array implicitly inherits from System.Array, which provide arrays with a number of interesting methods, among them support for the terms of the IEnumerable contract.

Example 2

2. Delegates and Events:
In Java, events are modeled after the "Listener" design pattern. Objects which generate events provide a set of methods to add or remove "listener" objects. These listener objects implement a particular interface specified by the event generating object. Events are generated when the event generating object calls methods on the registered event listener object.

From the standpoint of the Java runtime, events are nothing special. Event registration methods are simply methods on the event generating object, and listener objects are little more than objects that implement a particular event listening interface.

In order for builder tools to be able to detect which methods are events, therefore, Java programmers follow certain naming conventions for their registration methods. The event registration method follows the pattern addXYZListener, while deregistration is handled by removeXYZListener, with "XYZ" replaced by some unique identifying name for your event. The listener interface name must follow a similar pattern, in this case XYZListener (simply truncate the add or remove from the registration methods). This is the default pattern, though extra event methods which don't follow this pattern can be returned for JavaBeans that implement the BeanInfo interface.

.NET follows the Listener pattern, but with a new twist. First, .NET includes "delegates" which are a special type of object that wraps a method, static or member, that corresponds to a particular signature. Microsoft calls them "typesafe function pointers," which seems an apt description. A method wrapped inside an event delegate can be passed to an event property (which encapsulates the add and remove logic in one location) that is declared as part of an event generating class, and which is clearly marked in metadata as an event registration point. For instance, through reflection, you can ask any class for the events it supports by calling GetEvents() on its Type object (which is similar in concept to the Java class object), irrespective of naming convention.

Consider the following example:

Example 3

The example above will write "Received an event" to the console. The "type" of the event is the delegate type (rather than an interface, in Java's case), which might not be so clear in the shortened event declaration form, but is in the long form:

Example 4

Why is this an improvement over the Java mechanism for event handling? First, consider that event interfaces often have more than just one method to implement. The java.awt.event.MouseListener event interface used throughout the JFC and AWT has 5 methods to implement, while the java.awt.event.WindowListener has 7. Of course, adapter classes exist which implement this interface with a bunch of no-ops (MouseAdapter and WindowAdapter), but Java, like C#, only supports single inheritance. In other words, if an event handling class inherits from anything besides the default Object, the Adapter classes are useless.

Second, creating an event is simplified in .NET. I have rarely needed to use the long form for event declaration. The long form exists for cases where programmers need to perform extra processing in the course of event registration.

Third, events are clearly identified in the metadata associated with a type, which is a stronger identification mechanism than simple naming convention. This makes it easier for multiple languages, each with their own naming conventions, to target the common .NET runtime.

Event delegates are an easy way manage events in .NET applications. It's worth noting, though, that nothing stops the .NET developer from using Java-style event interfaces as needed. 99.9% of the time, .NET developers will find .NET's default event handling mechanism to be all they need. For those corner cases posed by critics as reason to avoid event delegates (though the referenced example seems rather contrived), that is always a fallback option.

3. No Exception Specifications/Support for Nested Exceptions:
In my experience as a Java programmer, it is very common to get an exception that is NOT the real cause of the problem. I track down the location in source code from which the exception was thrown (using stack trace information), only to find that the exception was thrown within a catch block, effectively hiding the lower level exception (and, potentially, the real cause of the problem) from me.. This is an accidental byproduct of a rule which says you have to declare in every method the exception(s) that will be thrown. Since this list can grow quite large for methods that call large numbers of exception throwing methods, it is common practice to specify a common base exception for your library, creating derived exceptions from that base as needed. Lower level exceptions are trapped within a method, and the application throws a new exception that meets the requirements of the exception specification.

This practice often results in root cause exceptions getting "eaten" by libraries higher up in the call stack. This is complicated by Java's lack of explicit support for nested exceptions in the base Exception classes. The end result is that that consumers of a library several steps removed from a lower level library often have no idea what is actually causing the problem, making it extremely hard to track down.

Some argue that programmers shouldn't rely on the details of a lower level exception. That's all well and good, but I at least want to know what the problem was. This is a case where it is better to trust that programmers aren't crazy enough to rely on details of a nested exception in programming logic (which in reality, most won't, simply because it takes work to do so) in order that programmers will have an easier time tracking down problems.

In other words, I find the lack of exception specifications a good thing, as it is now less likely that someone will hide the root cause of a problem from me (not impossible, mind you, just less likely). This point is open to debate, to be sure, so for a different point of view, this critique argues in favor of exception specifications, and against C#'s lack of them.

4. Properties:
Properties are a common programming paradigm. Even Java supports properties, though they are not uniquely identified as such in class metadata. Rather, Java programmers specify ordinary methods as properties by using the naming convention of "getXYZ" for property retrieval and "setXYZ" to set the value. Development environments know to look for this naming convention and treat values that follow the convention as properties.

In contrast, .NET in general and C# in particular includes explicit support for properties as separate entities from methods. System.Type includes methods for retrieving properties (System.Type.GetProperties and System.Type.GetProperty). In C#, the syntax for declaring and accessing these properties is as follows:

Example 5

I find this syntax appealing, if not life-changing. Others don't think so. They worry that use of a Property can hide the fact that you are, in fact, accessing a method on the class. As this critique claims, it might be possible to confuse a property in such a way that you hide an inefficiency in your application.

I'm not so sure that languages lacking C#-style properties are inherently safer, though. I regularly come across Java code that does something similar to the following:

Example 6

How does the presence of the "get" prefix make a programmer less likely to perform the previous operations, which potentially could be very expensive? In other words, you have to rely on programmers to write efficient code just as much with explicit property methods as without them. Good programming practice is the solution, not training wheels. The "remedy" of doing away with C#-style properties in order to avoid the rare case that someone forgets that they are using a property is outweighed by the syntactic benefit of having properties on classes.

5. Indexers:
C# allows objects to be treated like arrays through the use of an indexer. The indexer can take any type, and return any type of value. For instance, a collection object can provide access to its contents by index (an integer), by name (a string), or some arbitrary 3rd object type, as this example shows:

Example 7

The presence of indexers in C# explains why ArrayList and Hashtable can be accessed using array-style syntax. This should appeal to C++ programmers accustomed to STL (Standard Template Library) conventions. C# doesn't include support for generics (C++ templates are an example of generics), but that's coming, and in a form that might avoid the code bloat issues associated with C++ templates. If you want to try out the current state of generics support in C# and .NET, check out some of the ongoing work at Microsoft Research.

6. A preprocessor.
One of the reasons Java threw out the C-style preprocessor was the coding pitfalls associated with irresponsible use of macros. Preprocessors, however, aren't just used to make macros. They are also used to manage conditional compilation.

Conditional compilation allows programmers to include optional code into the final executable based on the presence of certain preprocessor constants. For instance, a common use of conditional compilation is for the inclusion of debug-related code. Such debug code might perform operations that you wouldn't want present in the released program. Conditional compilation allows this code to be included or excluded as necessary.

C# preprocessor support would be familiar to anyone accustomed to the C/C++ preprocessor. Constants are defined using #define, or as part of the build environment. Conditional statements use the #if, #else, #elif and #endif statements.

Example 8

7. The using code block.
In languages such as C++, you can determine exactly when memory will be freed for reuse. Resources can be released immediately through use of the delete operator, or at the end of a particular scope (such as a method declaration) for objects declared on the stack.

With Java and .NET, however, this is not the case. Memory is managed by the runtime, which means you cannot know exactly when your objects will be destroyed. This is a problem in cases where you are using resources that need to freed for reuse as soon as possible. Good examples include open files with exclusive read/write access, and database connections retrieved from a shared pool.

The common paradigm in Java is to define a "close()" method on objects that require such immediate cleanup. This method needs to be called in all cases, whether or not a particular block of code has exited normally. The standard way to do this is to place the call to close in the "finally" clause of a "try" exception handling block, like so:

Example 9

.NET requires the same kind of immediate cleanup, and the syntax for Java would work verbatim in C#. C#, however, has added the "using" keyword to simplify this common operation.

Example 10

This is performs the exact same function as:

Example 11

The "using" keyword works on any object which implements the "IDisposable" interface (which defines one method, void Dispose()).

8. More flexible source code organization.
Java classes must be defined in a file named after the public class it contains (only one of which can exist in the file). Furthermore, the programmer must place the source file in a directory structure that matches the package associated with the class. For instance, if the package directive at the top of the page reads "turtleneck.core", your source files must usually be located in a directory named turtleneck\core.

I've never liked this restriction. For small applications, you don't need such a strict code hierarchy, and either way, it should be up to developers to decide where to place their files.

C# gets rid of this requirement. This might lead to longer source code files, as developers might choose to put more than one public class in a source file. C# remedies this issue through use of the "#region" precompiler directive. As an example:

Example 12

In Visual Studio.NET and SharpDevelop (an open-source development tool for .NET), this block appears as a collapsed tree node identified by the name to the right of the #region tag. You can open and close this node by clicking on the "+" or "-" sign to the left of the named region.

9. Really simple native invocation:
This will make Java purists scream, as Java isn't supposed to need native invocation. Perhaps that deep-seated belief is what guided the Java development team to turn native code execution into a programmer's version of the Chinese water torture. JNI is a HORRIBLY messy C API, and anyone who likes it probably hangs around New York's Little Italy spitting half-chewed Milk Duds at Tony Soprano look-a-likes. There are LEGITIMATE REASONS for needing to call into native code, if for no other reason than that we don't live in a world where everything can be done in managed code (read: inside a Java VM or the .NET runtime).

C# makes it really simple to specify a library function for use in a C# class, as the following example shows:

Example 13

That's all I had to do to make a function in the CryptoAPI available to a program written in C#.

10. Attributes:
Attributes aren't exclusive to C# as they are defined as part of the .NET CLI. However, since C# is a .NET language and makes their use almost trivial, they are worth a mention. As this example code shows, it is very easy to mark up classes with lots of out of band information that can be accessed at runtime to control how the class is used.

Example 14

ALL bracketed information will be stored as part of the assembly as serialized data in the case of custom attributes, or as special flags on the object for special attributes (such as "Serializable") which are directly supported by the .NET runtime. Attributes can be applied at the class, interface, struct, method, property, parameter and return value level.

This is better than the source code markup used by XDoclet (the API promoted in Talkbacks to a previous article as Java's answer to metadata attributes). Data defined as .NET attributes are stored with the class definition and are available at runtime. XDoclet, in contrast, is source code markup built using custom JavaDoc tags. This is information that is NOT available at runtime, and is primarily a tool for source code generation during or prior to program compilation, not a standardized mechanism for plugging into common runtime services. The folks at Java recognize the need for true attributes, however, and plan to offer support for aspect-oriented programming with JDK 1.5.

Conclusion
C#, for all intensive purposes, is the premier language of .NET. It was written explicitly to support features found in .NET, and has become the de facto language used in .NET-oriented training materials. In other words, most .NET programmers will come across C# code frequently, and will acquire a strong familiarity with the language.

C# is an interesting language, and I hope that I've given you a sense of how some of the interesting aspects of the language work. For those interested in .NET programming, C# is a worthwhile addition to the programmer's toolkit. For those not interested in .NET programming, it is still worthwhile, if nothing else than as a source of ideas.

biography
John Carroll is a software engineer living in Ireland. He specializes in the design and development of distributed systems using Java and .Net. He is also the founder of Turtleneck Software.

Editorial standards