ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Comparing C# and Java

by Budi Kurniawan
06/07/2001

C# (C-Sharp) is Microsoft's new programming language, promoted as "the first component-oriented language in the C/C++ family." Despite the claim, however, many people think that C# is rather a clone of, or Microsoft's replacement for, Java. Is it true?

This article shows the evidence that C# is more than a sibling of Java. If you are a Java programmer who wants to learn or know more about C#, reading this article is the first ten minutes you should invest in.

C#, C++ and Java

Given the current hype, it is always interesting to compare C#, whose specification was written by Microsoft's Anders Hejlsberg and Scott Wiltamuth, with both C++ and Java. Considering the tone of recent IT newspapers' headlines, it is not too surprising if you already know that C# is closer to Java than to C++. For those who just joined the discussion, Table 1 below lets you see for yourself. Conclusion: Java and C# are not Siamese twins, but C#'s most important features are closer to Java than to C++.


Table 1: C#'s most important features compared with those of C++ and Java

Feature

C#

C++

Java

Inheritance

Single class inheritance, multiple interface implementation

Multiple class inheritance

Single class inheritance, multiple interface implementation

The notion of interface

Through the "interface" keyword

Through abstract class

Through the "interface" keyword

Memory management

Managed, using a garbage collector

Manual

Managed, using a garbage collector

Pointers

Yes, but only in the rarely-used unsafe mode. References are used, instead.

Yes, a very commonly used feature.

Not at all. References are used, instead.

Form of Compiled Source Code

.NET intermediate language (IL)

Executables.

Byte code.

One common base class

Yes

No

Yes

After these outlined important features, read on to find out some important differences between C# and Java.

Comparing the Language Specification

Primitives

Primitives in C# are called value types and there are more predefined value types than in Java. For example, C# has uint, or unsigned integer. Table 2 lists all the predefined types in C#.

Table 2: Value types in C#

Type

Description

object

The ultimate base type of all other types

string

String type; a string is a sequence of Unicode characters

sbyte

8-bit signed integral type

short

16-bit signed integral type

int

32-bit signed integral type

long

64-bit signed integral type

byte

8-bit unsigned integral type

ushort

16-bit unsigned integral type

uint

32-bit unsigned integral type

ulong

64-bit unsigned integral type

float

Single-precision floating point type

double

Double-precision floating point type

bool

Boolean type; a bool value is either true or false

char

Character type; a char value is a Unicode character

decimal

Precise decimal type with 28 significant digits

Constants

Forget the static final modifiers in Java. In C# constants can be declared using the const keyword.

public const int x = 55;

In addition, the designers of C# added the readonly keyword, which you can use if the constant value cannot be determined at compile time. These read-only fields can only be set through an initializer or a class constructor.

The entry point of a public class

In Java, the entry point of a public class is the public static method named main, which accepts an array of String objects as arguments and returns no value. In C#, the main method is the public static method called Main (with capital M), which also accepts an array of String objects and returns no value, as given in the following signature.

public static void Main(String[] args)

However, there is more. If you are not passing anything to the Main method, you can use an overload of Main, the one without an argument list. Therefore, the following Main method is also a valid entry point.

public static void Main()

Furthermore, the Main method can also return an int, if you want. For example, the Main method of the following code returns 1.

using System;
public class Hello {
public static int Main() {
Console.WriteLine("Done");
return 1;
}
}

As a comparison, overloading the main method is illegal in Java.

The switch statement

Unlike in Java, where the switch statement can only be used on integers, in C# switch can also work with string variables. Consider the following C# code that uses the switch statement with a string variable.

using System;
public class Hello {
public static void Main(String[] args) {
switch (args[0]) {
case "boss":
Console.WriteLine("Good morning, Sir. We are ready to serve you.");
break;
case "employee":
Console.WriteLine("Good morning. You can start working now.");
break;
default:
Console.WriteLine("Good morning. How are you today?");
break;
}
}
}

Unlike in Java, the switch statement in C# does not allow fall-through when reading code by requiring that each case block have either a break at the end of the block or a goto another case label in the switch.

The foreach statement

A foreach statement enumerates the elements of a collection, executing a statement for each element of the collection. Consider the following code.

using System;
public class Hello {
public static void Main(String[] args) {
foreach (String arg in args)
Console.WriteLine(arg);
}
}

If you pass arguments when calling the executable, such as the following

Hello Peter Kevin Richard

The output will be the following lines of text.

Peter
Kevin
Richard

There is no >>> Shift Operator in C#

In C#, there exist unsigned variable types such as uint and ulong. Therefore, in C# the right shift operator (>>) works differently on unsigned variable types and signed variables (such as int and long). Right shifting uint or ulong discards the low-order bits and sets high-order empty bit positions to zero. With int and long variables, however, the >> operator discards the low-order bits and set the high-order empty bit positions to zero only if the variable value is positive. For operations on a negative number, the high-order empty bit positions are set to 1.

In Java, there is no unsigned variable. Therefore, you use the >>> operator to include negative bits in right shifting, and use the >> operator otherwise.

goto

In Java, goto is a keyword that is not used. In C# goto brings you to the specified label. However, C# treats goto with extra care. For instance, a goto cannot be used to jump into a statement block. In Java, you use labeled statements with break or continue to replace goto in C#.

Declaring an Array

Declaring an array in Java is very flexible. Indeed, there are a number of forms that are all legal. For example, the following lines of code are equivalent.

int[] x = { 0, 1, 2, 3 };
int x[] = { 0, 1, 2, 3 };

However, in C# only the first line is valid. The [] cannot be placed after the variable name.

Packaging

A package in C# is called a namespace. To import a namespace in C# you use the word "using." The following code imports the System namespace for use.

using System;

However, unlike in Java, C# allows the use of an alias for a namespace or a class in a namespace.

using TheConsole = System.Console;
public class Hello {
public static void Main() {
TheConsole.WriteLine("Using an alias");
}
}

Conceptually, Java packages are the same as .NET namespaces. However, implementations are different. In Java, package names are also a physical thing that determines the directory structure where your .java files must reside. In C#, there is a complete separation between physical packaging and logical naming, so the name for your namespace has nothing to do with the physical packaging. In C#, each source file can contribute to multiple namespaces and can take multiple public classes.

The physical packaging in .NET is called assembly. Each assembly contains a manifest that enumerates the files that are contained in the assembly, controls what types and resources are exposed outside the assembly, and maps references from those types and resources to the files that contain the types and resources. Assemblies are self-contained and an assembly can be contained in a single file or be split among a number of files. This packaging mechanism solves the problem with DLL files, notoriously known as DLL Hell.

The Default Package

In Java, the java.lang package is the default package that is automatically included without having to be imported. To output a piece of text to the console, you can write the following code.

System.out.println("Hello world from Java");

In C#, there is no default package. To output text to the console, you use the WriteLine method of the Console object in the System namespace. However, you must always import the package explicitly. Thus, the following code.

using System;
public class Hello {
public static void Main() {
Console.WriteLine("Hello world from C#");
}
}

Object-Orientation

Java and C# are both fully object-oriented languages. In terms of the three principles of object-oriented programming, they could not be more similar.

Accessibility

Each member of a class has a form of accessibility. The access modifiers in C# are comparable to those in Java, with the addition of internal. In short, there are five forms of accessibility in C#, as given below.

Extending a Class

You use the keyword "extends" when performing inheritance in Java. C# adopts the C++ style when extending a class. For instance, the following code is how you create a new class named Button by extending a parent class called Control.

public class Button: Control {
.
.
.
}

Final Classes

Since there is no final keyword in C#, if you do not want your class to be extended, you can use the sealed keyword, like in the following example.

sealed class FinalClass {
.
.
.
}

Interfaces

The notion of interface in C# is very much like that in Java. There is the keyword interface and an interface can extend one or more other interfaces. By convention, an interface name starts with the capital I. The following code is an example of an interface in C#, which is indistinguishable from an interface in Java.

interface IShape {
void Draw();
}

The syntax for extending an interface is the same as extending a class. For example, the IRectangularShape interface below extends the IShape interface.

interface IRectangularShape: IShape {
int GetWidth();
}

If you are extending from two or more interfaces, the list of parent interfaces are separated by commas, like in the following code.

interface INewInterface: IParent1, IParent2 {
}

Unlike Java, however, an interface in C# must not contain fields.

Note also that in C#, all methods in an interface are public by default. Unlike Java, where the modifier public could be present in a method signature (even though this is not necessary), explicitly specifying an interface method as public is illegal in C#. For example, the following interface will generate a compile error in C#.

interface IShape {
public void Draw();
}

The is and as Operators

The is operator in C# is the same as the instanceof operator in Java. Both can be used to test whether or not an instance of an object is of a particular type. The as operator in C# has no equivalent in Java. It is very similar to the is operator, but it is more aggressive in that it also tries to convert the tested object reference into the type in question, if the type is correct. If not, the variable reference will be set to null.

To really understand how as operates, consider the use of is in the following code, where there is an interface called IShape and two classes (Rectangle and Circle) that both implement IShape.

using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
if (shape is Rectangle) {
Rectangle rectangle = (Rectangle) shape;
Console.WriteLine("Width : " + rectangle.GetWidth());
}
if (shape is Circle) {
Circle circle = (Circle) shape;
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}

After the code is compiled, the user can enter either "rectangle" or "circle" as a shape in args[0] of the Main method. If "circle" is entered, shape is then instantiated to a Circle object. On the other hand, if the user types in "rectangle," shape is instantiated to a Rectangle. The shape is then tested for its object type using the is operator. If it is a rectangle, then shape is cast to a Rectangle object and its GetWidth method is called. If it is a circle, shape is cast to a Circle object and its GetRadius method is called.

Using the as operator, the example code above can be modified as in the following.

using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
Rectangle rectangle = shape as Rectangle;
if (rectangle != null) {
Console.WriteLine("Width : " + rectangle.GetWidth());
}
else {
Circle circle = shape as Circle;
if (circle != null) 
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}

In the bold lines in the code above, as is used to convert shape to Rectangle without first testing its object type. If shape is indeed a Rectangle, shape is cast into rectangle as a Rectangle object and its GetWidth method is called. If the conversion fails, a second attempt is performed. This time, shape is cast into circle as a Circle object. If shape is really a Circle object, circle will now reference to the Circle object and its GetRadius method is invoked.

Libraries

C# does not have its own class libraries. However, it shares the .NET class libraries that can be used in other .NET languages such as VB.NET or JScript.NET. Something worth noting is the StringBuilder class, which complements the String class. The StringBuilder class is very similar to Java's StringBuffer.

Garbage Collection

C++ has taught us how inefficient and time-consuming it is to deal with memory management manually. When you create an object in C++, you have to destroy it manually. As code becomes more complex, this task becomes increasingly difficult. Java solved this problem using the garbage collection method that collects unused objects and releases the memory. C# follows suit; however, this is a very natural path to take if you are developing a new OOP language. C# still preserves the C++ way of managing memory manually when speed is in extreme need, something which is taboo in Java.

Exception Handling

You would not be surprised to find out that C# uses an error handling mechanism similar to Java's, would you? In C# all exceptions are derived from the class named Exception. (Aha, why does this sound familiar?) And, yes, you have the familiar try and catch statement like in Java. This Exception class is part of the .NET System namespace.

What Java Does Not Have

Born after Java was already mature, it is no surprise that C# has some nice features that Java does not have (yet).

Enumerators (enums)

An enumerator is a set of related constants. To be exact, an enum type declaration defines a type name for a related group of symbolic constants. For example, you can create an enumerator called Fruit and use it as the value type of a variable to limit the possible values of the variable to those specified in the enumerator.

public class Demo {
public enum Fruit {
Apple, Banana, Cherry, Durian
}
public void Process(Fruit fruit) {
switch (fruit) {
case Fruit.Apple:
...
break;
case Fruit.Banana:
...
break;
case Fruit.Cherry:
...
break;
case Fruit.Durian:
...
break;
}
}
}

In the Process method of the example above, surely you can use an int as the value type of the myVar variable. However, using the enum Fruit limits the possible values to Apple, Banana, Cherry or Durian. Compared to int, enum is more readable and self-documenting.

Structs

A struct is very similar to a class. However, while a class is created in the heap as a reference type, a struct is a value type that is stored on the stack or in-line. Therefore, used with care, structs are faster than classes. A struct can implement interfaces and have the same kinds of members as a class, but a struct does not support inheritance.

However, simply replacing a class with a struct can be disastrous. Because a struct is passed by value, a "fat" struct is slower to pass around because values must be copied to a new place. In the case of a class, only the reference to the class is passed around.

The following is an example of a struct. Note how similar it is with a class. Substituting the word "class" for "struct" gives you a class.

struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

Properties

In addition to fields, a C# class can also have properties. A property is a named attribute associated with an object or a class. Properties are a natural extension of fields -- both are named members with associated types, and the syntax for accessing fields and properties is the same. However, unlike fields, properties do not denote storage locations. Instead, properties have accessors that specify the statements to execute in order to read or write their values. Properties thus provide a mechanism for associating actions with the reading and writing of an object's attributes, and they furthermore permit such attributes to be computed.

In C#, properties are defined using property declaration syntax. The first part of the syntax looks quite similar to a field declaration. The second part includes a get accessor and/or a set accessor. In the example below, the PropertyDemo class defines a Prop property.

public class PropertyDemo {
private string prop;
public string Prop {
get {
return prop;
}
set {
prop = value;
}
}
}

Properties that can be read and written, like the Prop property in the PropertyDemo class, include both get and set accessors. The get accessor is called when the property's value is read; the set accessor is called when the property's value is written. In a set accessor, the new value for the property is given in an implicit value parameter.

A property can be read and written in the same way that fields can be read and written. For example, the following code instantiates the PropertyDemo class and writes and reads its Prop property.

PropertyDemo pd = new PropertyDemo();
pd.Prop = "123"; // set
string s = pd.Prop; // get

Passing Primitive Parameters By Reference

In Java, when you pass a primitive as a parameter to a method, the parameter is always passed by value -- i.e., a new copy of the parameter is created for that method. In C#, you can pass a primitive (value type) by reference. If you do this, the method uses the same variable passed to it -- i.e., if you change the value of the value type passed, the original variable is changed.

To pass a value type by reference in C#, you use the keyword ref. For example, if you compile and run the following C# code, you will get 16 in the console. Note how the value of i is changed after being passed to the ProcessNumber method.

using System;
public class PassByReference {
public static void Main(String[] args) {
int i = 8;
ProcessNumber(ref i);
Console.WriteLine(i);
}
public static void ProcessNumber(ref int j) {
j = 16;
}
}

There is also another keyword named out. This is similar to ref, allowing a value type to be passed by reference; however, the variable passed as the parameter does not have to have a known value before the passing. The example above will generate an error message if the int i is not initialized before being passed to the ProcessNumber method. If you use out instead of ref, you can pass a value that has not been initialized, like in the following modified example.

using System;
public class PassByReference {
public static void Main(String[] args) {
int i;
ProcessNumber(out i);
Console.WriteLine(i);
}
public static void ProcessNumber(out int j) {
j = 16;
}
}

This time the class PassByReference compiles fine, even though i is not initialized prior to passing it to the method ProcessNumber.

Pointers Are Still Available in C#

Developers who think they can handle pointers wisely and are happy with manual memory management can get some extra horsepower in performance by using the "old time" pointers which are neither safe nor easy. C# provides the ability to write "unsafe" code. Such code can deal directly with pointer types, and fix objects to temporarily prevent the garbage collector from moving them. This "unsafe" code feature is in fact a "safe" feature from the perspective of both developers and users. Unsafe code must be clearly marked in the code with the modifier unsafe, so developers can't possibly use unsafe features accidentally, and the C# compiler and the execution engine work together to ensure that unsafe code cannot masquerade as safe code.

using System;
class UsePointer {
unsafe static void PointerDemo(byte[] arr) {
.
.
.
}
}

Unsafe code is used in C# when speed is extremely important or when your object needs to interface with existing software, such as COM objects or native C code in DLLs.

Delegates

Delegates can be thought of as function pointers in C++ and other languages. Unlike function pointers, however, delegates in C# are object-oriented, type-safe, and secure. And, while a function pointer can only be used to reference a static function, a delegate can reference both a static method and an instance method. A delegate is used to encapsulate a callable method. You can write a method in a class and create a delegate on that method. The delegate can then be passed to a second method. This second method can then call the first method.

Delegates are reference types that derive from a common base class: System.Delegate. There are three steps in defining and using delegates: declaration, instantiation, and invocation. Delegates are declared using the delegate declaration syntax. A delegate that takes no arguments and returns void can be declared with the following code.

delegate void TheDelegate();

A delegate instance can be instantiated using the new keyword, and referencing either an instance or class method that conforms to the signature specified by the delegate. Once a delegate has been instantiated, it can be called using method call syntax.

Boxing and Unboxing

In an object-oriented language, you normally work with objects, but primitives are also provided for the sake of speed, so you have a world of objects and another world of values. In this situation there is always a need to make both worlds able to work together. You invariably need a way to make references and values communicate.

In C# and .NET Runtime worlds, this "communication" problem is solved using boxing and unboxing. Boxing is a process to make value types look like reference types. Boxing happens automatically when a value type (a primitive) is used in a location that requires or could use an object. Boxing a value of a value-type consists of allocating an object instance and copying the value-type value into that instance.

Unboxing does the reverse of what boxing does. It converts a reference type into a value type. An unboxing operation consists of first checking that the object instance is a boxed value of the given value-type, and then copying the value out of the instance.

Java tackles this problem somewhat differently by providing a class wrapper for each primitive, e.g. the Integer class for int and the Byte class for byte.

Summary

This article has shown you how C# and Java compare. They are very similar; however, it is probably too far to say that C# is a clone of Java. Things like object-orientation or intermediary languages are not new ideas. So, if you were to design a new object-oriented language that needs to run in a managed and safe environment, wouldn't you come up with something like C#?

Budi Kurniawan is a senior J2EE architect and author.


Return to .NET DevCenter.

Copyright © 2009 O'Reilly Media, Inc.