AddThis Social Bookmark Button

Print

Comparing C# and Java
Pages: 1, 2, 3, 4

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.