站内搜索: 请输入搜索关键词
当前页面: 图书首页 > NET For Java Developers Migrating To C#

NET For Java Developers Migrating To C#

[ directory ] Previous Section Next Section

3.4 Language Constructs, Syntax, and the API

This section compares the building blocks of C# and Java. We use the following categories:

  • Data types

  • Operators

  • Control flow statements

  • Exception support

  • Common objects (Object, String)

  • Common language elements

  • Collections library

  • Threading

  • Input/output

  • Namespaces and assemblies

  • What is missing from Java?

  • What is missing from C#?

3.4.1 Data Types

C# allows for both safe and unsafe code. You can use unsafe code to do the kind of pointer programming supported in C and C++. Safe code is what Java programmers are used to, and one of the reasons it is called safe is that type safety is guaranteed by the runtime for safe code. Table 3.3 compares some of the key points of similarity between Java and C# data types.

Built-in C# value types are of the same type as those in Java except for the unsigned short, int, and long, which are absent from Java. C# also provides a 12-byte decimal, also absent from Java.

One major difference between C# and Java is the way C# transparently interchanges primitives and their respective System classes depending on the context. This process of boxing (converting a primitive to its System namespace equivalent) and unboxing is transparent to the programmer. Therefore, unlike Java's strict treatment of primitives and their wrapper classes, boxing is highly convenient. In C++, both structs and classes (objects) can be allocated on the stack, in-line, or on the heap.

Table 3.3. Java and C# Data Types

Data Type

Java

C#

Reference types

Classes, interfaces, and arrays

Classes, interfaces, arrays, and delegates

Value types

Primitives only

Cannot use new operator to instantiate value types

Primitives, structs, and enum. All value types are allocated on the stack.

 

No support for unsigned short, int, or long

Can use the new operator to instantiate value types

  

Support for unsigned short, int, and long

Pointer types

No support

Limited form of C- and C++- style pointer programming

Transparent conversion of primitive data type to its wrapper

No support

Supported through boxing and unboxing. Each primitive is mapped to a class in the System namespace and therefore can be treated like a regular object without doing any additional wrapping.

In C#, structs are always created on the stack or in-line, and classes (objects) are always created on the heap. Structs indeed let you create more efficient code.

C# also preserves the C and C++ enum construct. Java programmers have learned to cope with the lack of enum support by using hand-crafted, type-safe enumeration classes, which are essentially wrapper classes for holding constant values.

In C#:

public enum Direction {North=1, East=2, West=4, South=8};

In Java:

public class Direction {
  public static Direction North = new Direction(1);
  public static Direction East = new Direction(2);
  public static Direction West = new Direction(4);
  public static Direction South = new Direction(8);
  private int type;
  private Direction(int I) { this.type = i; }
}

3.4.2 Operators

Table 3.4 compares Java and C# operators of different categories. The operators have the same default meanings in both languages.

In C#, you can overload operators to customize the meanings of your custom classes. Java does not allow end-user operator overloading, although the API itself overloads some operators for end-user convenience (such as + operators on strings). User-defined operator declarations cannot modify the syntax, precedence, or associativity of an operator, and user-defined operators cannot have the same signature as the predefined operators.

Table 3.4. Java and C# Operators

Operator Category

Java

C#

Arithmetic

+, -, *, /, %

+, -, *, /, %

Logical

&&, ||, true, false, !

&&, ||, true, false, !

Bitwise

&, |, ^, /, ~

&, |, ^, ~

String concatenation

+

+

Increment, decrement

++, --

++, --

Shift

>>, <<

>>, <<

Relational

==, <=, >=, <, >, !=

==, <=, >=, <, >, !=

Assignment

=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

Member access

.

.

Casting

()

()

Indexing

[]

[]

Conditional

?:

?:

Object creation

new

new

Type information

instance of

is, as, sizeof, typeof

Overflow exception control

None

checked, unchecked

Pointer-based

None

*, &, ->, []

User-defined operators (if defined) take precedence over the default operators. C# ensures at compile time that both operators in logical pairs (!= and ==, > and <, >= and <=) are defined.

3.4.3 Control Flow Statements

Table 3.5 compares the Java and C# control flow structures. Note that all control flow structures have the same meaning in the two languages.

The foreach() statement is a new statement not supported in Java. It is used for iterating over collections of objects of type class, interface, or struct. It can be used as follows:

foreach (object o in collection) {
}

Table 3.5. Java and C# Control Flow Structures

Control Flow Statement

Java

C#

Conditional

if-elseif-else
  switch(condition)
if-elseif-else
  switch (condition)

Iterative

while (condition) {}
  do{ } while (condition)
  for(start;condition;step)
  N/A
while (condition) {}
  do{ }while (condition)
  for(start;condition;step)
  foreach()

Jump statements

break, continue

break; continue, goto

This statement iterates through the collection.

The C# switch statement also is a little different from Java's switch-case construct. The C# switch statement allows you to use strings in the case block of the switch statement. This comparison is case-sensitive. Therefore, the following can be done in C# but not in Java:

switch (name) {
case "Jack":break;
case "Jill":break;
};

A C# goto statement must point to a label or one of the options in a switch statement. This is similar to the continue label statement in Java. A goto statement can point anywhere within its scope, and that restricts it to the same method (or finally block if it is declared within one). It cannot jump into a loop statement that it is not within, and it cannot leave a try block before the enclosing finally blocks are executed. The continue statement in C# is equivalent to the continue statement in Java, except that the C# version can't point to a label.

3.4.4 Exception Support

Both Java and C# support the try-catch-finally construct for dealing with exceptional conditions. Programming with exceptions lends direction to the flow of execution of your program when things go wrong. The try-catch-finally construct in both languages follows the same rules. Table 3.6 lists the similarities and differences.

Table 3.6. Java and C# Exception Support

Exception Element

Java

C#

Single exception class from which all other exceptions derive

java.lang.Throwable

System.Exception

Exceptions and errors

Differentiates between exceptions (java.lang.Exception) and errors (java.lang.Error). Errors tend to indicate more fatal failures. Both inherit from java.lang.Throwable.

Does not differentiate between errors and exceptions.

Checked and unchecked exceptions

Supports checked and unchecked exceptions.

No support for checked and unchecked exceptions. All exceptions are unchecked.

throws clause

throws clause is an integral part of a method signature and clearly indicates which exceptions are getting thrown.

No throws clause. Only way to know which exceptions are being thrown is to read the code or trust the documentation!

 

Classes implementing interfaces with a throws clause in the method signature must adhere to only those exceptions.

Classes implementing interfaces can throw exceptions not explicitly declared in the methods on the interfaces.

Inheritance

Subclasses cannot throw new exceptions in over-ridden methods of the superclass.

Because of method hiding, you can have a subclass throw an exception not formally thrown by the superclass method.

3.4.5 Common Objects

The java.lang.Object and java.lang.String classes are the two ubiquitous classes of the Java API. Their C# equivalents are no less ubiquitous. Tables 3.7 and 3.8 compare java.lang.Object and java.lang.String to their C# equivalents.

Table 3.7. Java and C# Root Object Classes

Java (java.lang.Object)

C# (System.Object)

toString()

ToString()

equals()

Equals()

Object()

Object()

hashCode()

GetHashCode()

getClass()

GetType()

finalize()

Finalize()

clone()

MemberWiseClone()

Table 3.8. Java and C# String Classes

String Class Element

Java

C#

Immutable

Yes

Yes

Can be inherited from?

No (final class)

No (sealed class)

Supports interning

Yes

Yes

Comparing string values

equals() method

== operator

Comparing string object instances

== operator: s == t

== operator on cast object: (object) s == (object) t

String concatenation

Object creation if one of the concatenated strings is not a literal.

No object creation. Safe as using the StringBuilder class.

Interfaces implemented

Comparable, Serializable

IComparable, ICloneable, IConvertible, IEnumerable

The java.lang.Object class of Java and the System.Object class of C# are the root classes from which all other classes inherit. From Table 3.7, it is apparent that both C# and Java maintain a striking similarity in the method names, signatures, and functionality. Note that the C# object has an alias keyword object (with a lowercase o) that can be used instead of the fully qualified System.Object. Also, all value types in C# inherit from System.Object through boxing.

In both languages, string objects are immutable. Moreover, because they are ubiquitous, the runtime optimizes string literals by interning them: representing the same literal by the same object on the heap. The one major difference in C# is that the == operator for System.String is overloaded to compare the strings for their values and therefore is equivalent to Java's equals method. Also, to compare two string instances you cast them to their object equivalents and then use the == operator.

3.4.6 Common Language Elements

This section briefly compares some of the core language elements common to C# and Java.

Classes

Classes form one of the fundamental language elements in C# and Java. Here's a typical class signature:

attributes class-modifiers class class-name class-base {}

Optional parts of the signature are italicized. Notice that almost everything except for the class name and the keyword class is optional in both languages. Attributes are applicable only in C#. In Java, classes are closely tied to the actual file in which they are written (a .java file); according to the Sun JDK, a public class must be declared in a .java file whose name is the same as the class name. In C#, classes are not tied to the concept of a .cs file. A .cs file can contain any number of public classes.

Classes in both C# and Java can nest inner classes. Accessibility of a class determined by the access modifier on the class, and the set of applicable access modifiers that can be used on a class is determined by the context in which the class is declared. For example, in Java a .java file must have one public class defined. Absence of an access modifier gives default accessibility to the class. The default accessibility is internal for a nonnested C# class, and private for a nested C# class. The default class access level for both nested and nonnested Java classes is package level (similar to the default access level of fields and methods). In addition to access modifiers, you can use other modifiers to indicate what kind of a class is being created. Table 3.9 compares the Java and C# class modifiers.

You can nest classes in both Java and C#. Nested classes in Java can be regular inner classes or anonymous classes, and they can have the same class access modifiers as those of nonnested Java classes. In C#, nested classes can have the same access modifiers as those of nonnested types. In C#, a nested class has access to all the members that are accessible to its containing class, including members of the containing class that have declared private and protected accessibility. A nested class also can access protected members defined in a superclass of its containing class.

Table 3.9. Java and C# Class Modifiers

Java

C#

Meaning

public

public

A public Java class can be accessed from any package

A public C# class can be accessed from any namespace.

protected

protected

A protected Java class can be accessed only by classes within that package.

A protected C# class is declared as protected, and its type is accessible by a containing type and by any type that inherits from this containing type. This modifier should be used only for internal classes.

N/A

internal

If a class is declared as internal, the type it defines is accessible only to types within the same assembly. This is the default access level of nonnested classes.

N/A

protected internal

The permissions allowed by this access level are those allowed by the protected level plus those allowed by the internal level. The access level is thus more liberal than its parts taken individually. This modifier should be used only for internal classes (classes declared within other classes).

private

private

In Java, a private class has visibility only in the file where it is declared. A private class is always declared inside another class.

In C#, when a class is declared as private, access to the type it defines is limited to a containing type only. This modifier should be used only for internal classes.

static

N/A

A static class is used to declare a top-level class as opposed to an inner class.

N/A

new

The new keyword can be used for nested classes. A nested class is one that is defined in the body of another class; it is in most ways identical to a class defined in the normal way, but its access level cannot be more liberal than that of the class in which it is defined. A nested class should be declared using the new keyword just in case it has the same name as (and thus overrides) an inherited type.

final

sealed

A final Java class and a sealed C# class cannot have subclasses.

abstract

abstract

In both C# and Java, abstract classes have one or more abstract methods. An abstract class cannot be instantiated.

Constructors

Constructors are special methods that are used to create instances of a particular class. Constructors can be instance constructors or static constructors. Both C# and Java support instance constructors. Static constructors are C#-specific, and static blocks in Java are somewhat similar to static constructors.

If you do not provide an instance constructor for your class, both languages provide a default no-argument public instance constructor. However, if you do provide a constructor, the default constructor is not provided. You can have any access modifier on a constructor. When a class declares only private instance constructors, it is not possible for other classes to derive from that class or to create instances of that class.

A utility class consisting of only static methods typically uses a private constructor. Private constructors can also be used to create singleton classes (classes having only one instance object).

Static constructors are like Java's static blocks, and they are used to initialize classes. The acceptable modifiers on a static constructor are extern and static.

Destructors

C# introduces the concept of destructors. Unlike C++, C# destructors are not deterministic. You cannot call a C# destructor and expect the memory associated with the object to be reclaimed. Memory management is done exclusively by the garbage collector (as in Java), but a destructor is like Java's finalize method. Destructors are invoked automatically and cannot be invoked explicitly. When an instance is destroyed, the destructors in that instance's inheritance chain are called, in order, from most derived to least derived. You can implement a destructor by overriding the virtual method finalize on System.Object. In any event, C# programs are not permitted to override this method or to call it (or overrides of it) directly.

Class Members

Class members in C# can be fields, methods, constants, delegates, operators, indexers, and properties. In Java there are only fields and methods.

Let's compare the field and method members. Class fields and methods in Java and C# share similar declaration syntax. They all have associated modifiers that specify where (access level) and how (context) a member can be used. The member static modifier makes the member static and hence accessible only in static contexts. Similarly, a nonstatic member can be accessed only from a nonstatic context. The following paragraphs discuss variables (which are what class fields are) and methods.

Variables

Variables represent storage locations. Variables point to values that are of a specific data type. To indicate this relationship between a variable and its value, variables are declared as having the same type as the value they are pointing to. C# and Java are both type-safe languages. The runtime environment guarantees that a variable of type T always points to a value of the same type T.

C# and Java do not support the C and C++ concept of differentiating between defining a variable and declaring it. In C# and Java, you just declare the variable.

Variables have scope depending on where they are declared. Variables also must be initialized (again, depending on the scope in which they are declared). Variables have default values.

Table 3.10 compares the variable categories in Java and C#. Regardless of the Table 3.10 categories, for variables pointing to value types, you obtain the default value by calling the constructor on the value type. For variables pointing to reference types, the default value is the null reference.

Methods

A typical Java or C# method declaration looks like this:


attributes method-modifiers return-type method-name (formal parameter list) 
graphics/ccc.gifthrows exceptions

Table 3.10. Variables in Java and C#

Variable Category

Java

C#

Static variables

A field declared in a static context (static block) or static class member.

A field declared in a static constructor or a static class member.

 

Automatically initialized to the default value of the variable's type.

Automatically initialized to the default value of the variable's type.

 

Modifiers that go with static variables: private, public, protected, volatile, final

Modifiers that go with static variables: private, public, protected, internal, readonly, volatile

Class instance variables

A field declared without the static modifier is an instance variable.

A field declared without the static modifier is an instance variable.

 

Automatically initialized to the default value of the variable's type.

Automatically initialized to the default value of the variable's type.

 

Modifiers that go with instance variables: private, public, protected, volatile, final

Modifiers that go with instance variables: private, public, protected, internal, volatile, const, readonly

Struct instance variables

N/A

An instance variable of a struct has exactly the same lifetime as the struct variable to which it belongs.

The initial assignment state of an instance variable of a struct is the same as that of the containing struct variable.

Value parameter variables

Method parameters are passed by value and are called value parameters.

Method parameters are passed by value and are called value parameters.

 

A value parameter variable ceases to exist after the method call.

A value parameter variable ceases to exist after the method call.

 

Variables pointing to primitive types do not retain changes made to them inside the method (passed by value).

Variables pointing to primitive types do not retain changes made to them inside the method (passed by value).

 

These are initialized with the value passed by the calling method.

These are initialized with the value passed by the calling method.

Reference parameter variables

N/A

Allows method parameters to be passed by reference as opposed to the default passed by value.

A parameter declared with a ref modifier is a reference parameter.

Value of the reference parameter is the same as that of the underlying variable.

Variables must be assigned before they are passed in as ref parameters.

Output parameter variables

N/A

A parameter declared with a ref modifier is a reference parameter.

Value of the reference parameter is the same as that of the underlying variable. Variables need not be assigned before they are passed in as ref parameters but must be assigned before the containing method returns.

Useful for returning multiple values from a single method call.

Local variables

Local variables have the narrowest scope and cease to exist as soon as the block in which they are declared ends.

Local variables have the narrowest scope and cease to exist as soon as the block in which they are declared ends.

 

Local variables are never initialized by default. You must initialize them before using them.

Local variables are never initialized by default. You must initialize them before using them

 

Valid modifiers: volatile, final

Valid modifiers: const

Table 3.11. Java and C# Method Modifiers

Java

C#

Meaning

N/A

new

Used when a superclass method is marked with the virtual modifier and the corresponding subclass method uses the new modifier. The subclass hides the superclass implementation.

N/A

override

Used when a superclass method is marked with the virtual modifier and the corresponding subclass method uses the override modifier. The subclass overrides the superclass implementation.

N/A

virtual

Used in combination with the new and override modifiers.

public

public

Makes the method public to other classes in other namespaces or packages.

N/A

internal

Makes the members accessible to files within the same assembly.

protected

protected

Makes the method accessible to objects of this class and its subclass(es).

N/A

protected internal

The permissions allowed by this access level are those allowed by the protected level plus those allowed by the internal level. The access level is thus more liberal than its parts taken individually.

static

static

Class-level methods.

final

sealed

Subclasses cannot override a sealed or final method.

abstract

abstract

Abstract methods do not provide any implementation.

native

extern

These modifiers are used to identify methods that use external libraries or code.

Note that attributes (C#), method-modifiers, and parameter list are optional parts of a method signature. The throws clause of a method signature is optional and applies only to Java.

Method modifiers are used in combination. Table 3.11 compares the Java and C# method modifiers.

Certain method modifiers cannot be used in combination with each other. The following method modifier combinations are not allowed in C#:

  • abstract and sealed

  • abstract and static

  • abstract and override

  • static and override

  • override and new

  • override and virtual

  • abstract and extern

The following method modifier combinations are not allowed in Java:

  • abstract and final

  • abstract and static

  • abstract and native

You can overload methods in Java and C# by changing the parameter list of the method signature. Changing the return type of the attributes (C#) on a method does not overload the method. Overloaded methods have the same method modifiers.

Interfaces

Like a class construct, an interface is a first-class type in Java and C#. Here is a typical interface signature:

attributes modifiers interface name interface-base body;

Optional parts are italicized. Attributes are applicable only to C#. Also, Java interfaces can contain throws clauses, which indicate the checked exceptions thrown by the method.

An interface can inherit from zero or more interfaces, which are called the explicit base interfaces of the interface. The explicit base interfaces of an interface must be at least as accessible as the interface itself (that is, a public interface cannot derive from a private interface). A class that implements an interface also implicitly implements all of the interface's base interfaces. Like classes, interfaces can nest other interfaces. Interface method signatures follow the same rules as class method signatures except that interface methods have no associated body. A class can declare explicit interface member implementations. An explicit interface member implementation is a method, property, event, or indexer declaration that references a fully qualified interface member name. Explicit interface implementation is present only in C#.

3.4.7 Collections Library

The C# language defines its collection classes and interfaces in the System.Collection namespace. The C# ICollection interface closely matches the java.util.Collection interface. The IList and the IDictionary interfaces match the java.util.List and java.util.Map interfaces, respectively. Following are the differences between the C# and Java collections APIs:

  • C# has no separate implementations for thread-safe and thread-unsafe collections. By default, all collections are thread-unsafe, something that follows the Java 2 collections paradigm. Therefore, C#'s Hashtable is equivalent to Java's HashMap and not to java.util.Hashtable.

  • C# has no equivalent of the java.util.Collections class. This means that methods such as Reverse, Sort, and BinarySearch are on the concrete collection implementation (i.e., ArrayList, Hashtable) instead of being pulled out in a class with only static methods.

  • C# has no equivalent of java.util.Set.

  • C# collections can be accessed using array-like indexing syntax.

  • C# supports case-insensitive lists and Hashtables.

  • C# collections can hold primitives (because of boxing and unboxing).

3.4.8 Threading

In Java, you can create threads by extending the java.lang.Thread class or by implementing the java.lang.Runnable interface. Threads must be created manually, and the programmer is responsible for managing the life cycle of the thread (aborting it properly). Java also supports Timer classes for executing periodic asynchronous activities. Multiple threads can simultaneously access and change a given object's state. For an object to be thread-safe, it should allow only one thread to change its state at any given time. In Java, you implement thread safety by using the synchronize keyword.

Only one thread can access a synchronized method of an object. You can synchronize static and nonstatic methods or even blocks of code, synchronizing the object whose state is to be made thread-safe. The wait(), notify(), and notifyAll() methods on the java.lang.Object signal the calling thread to go into a wait state and wait for further notification.

C# supports four ways to create threads:

  1. Java-like manual construction and running of a thread. Recommended for long-running, coarse-grained threads that require finer control of the thread's life cycle.

  2. Using the System.Threading.ThreadPool class. C# provides a systemwide thread pool class that handles the life cycle of a thread C# doles out to the end user. Recommended for short-running, fine-grained tasks.

  3. Using the System.Threading.Timer class. This is the recommended approach for executing periodic tasks.

  4. Using the asynchronous callback mechanism. Although this approach does not directly create a thread, it simulates asynchronous behavior and provides a generic mechanism for information sharing between the caller thread and the execution thread.

You can implement thread safety in C# by using the lock keyword, which is similar to the synchronize keyword of Java. C# also provides classes in the System.Threading namespace that can be used for explicitly acquiring and releasing locks. C# provides reader/writer locks, which are useful for the typical programming problem of multiple threads trying to read an object and only one thread trying to write to it. The C# Interlocked class provides operations that can be executed on the int and long data types. These operations cannot be interrupted; they are atomic and therefore preclude the need for specifying locks for trivial tasks.

3.4.9 Input/Output

Both C# and Java deal with input/output through Stream classes, whereas Java differentiates between InputStream and OutputStream. C# maintains only one Stream class, which has a property to indicate whether it is for output or input. The subclasses of the Stream class (MemoryStream, FileStream, NetworkStream, and BufferedStream) are differentiated based on the underlying medium from which the sequence of bytes is read.

The Stream classes are useful for lower-level byte reading from the underlying medium. However, it is often convenient to use the corresponding Reader/Writer classes because they simplify the method calls. As of JDK 1.3, there is no nonblocking I/O in Java. To simulate nonblocking I/O behavior, you must initiate a thread that does the actual I/O. JDK 1.4 introduces the java.nio package, which simulates nonblocking behavior. In C#, nonblocking I/O is not a first-class concept (unlike JDK 1.4); instead, C# provides a convenient API so that writing nonblocking I/O is relatively easy. Most of the C# I/O classes have constructors that take an asynchronous callback delegate, and therefore the need to create a thread to simulate I/O is eliminated.

C# provides a rich API for dealing with files and directories. Unlike Java, C# does differentiate between a file and a directory. Note that in C#, IOException is an uncaught exception, and hence it won't be caught at compile time. In Java, IOException is a checked exception and is thrown by most java.io class methods, thereby forcing the user to account for I/O failures. You can activate basic serialization in C# by tagging a class with the Serializable attribute or implementing the ISerializable interface. You can use custom serialization to achieve more control over the serialization and deserialization process.

3.4.10 Namespaces and Assemblies

In Java, a unit of compilation is a single source file, typically containing a single public class. In practice, there is usually a one-to-one correspondence between the source file and the single class it defines. Because an application can contain multiple classes and because class names could very well collide, it is customary to arrange classes in packages. Absence of a package declaration in a class assumes the default package.

Packages have special meaning in Java, in the sense that they closely map to the disk structure in which the source files and the compiled class files are stored. Packages prevent naming conflicts and provide a logical organizational system for arranging classes based on functionality (for example, all GUI-related classes hang together in a single package). Packages also affect the accessibility of a class. A package-level class or interface is accessible only to other class and interface peers in the same package. To reference classes and interfaces in other packages, you use the import keyword. This lets you refer to the class or interface without specifying its fully qualified name.

Namespaces play a similar role in C#. Namespaces allow logical organization of C# types. You can refer to another namespace through the using keyword, which is a close equivalent of the import keyword. But using is more flexible because it can be used to alias a long, fully qualified name to something more user-friendly:

using GUIToolKit = com.software.programmer.gui.toolkit;

Here, if a class SplashScreen happens to be in that namespace, it can be referred to as GUIToolKit.SplashScreen elsewhere in the code.

Namespaces can be nested. Namespaces have no access restrictions. It is not possible to declare private, protected, or internal namespaces, and namespace names are always publicly accessible. This is similar to Java packages. A unit of compilation in C# is a source file. A source file can consist of multiple namespaces and multiple public classes. Source files can have namespaces and classes cross-referencing each other. Unlike Java, the C# namespace is not indicative of any directory structure on the file system.

Assemblies are EXEs or DLLs generated from compiling a project of files. To greatly simplify deployment, the .NET runtime uses the configurable attributes and versioning rules built into assemblies. Assemblies also form a boundary to deal with type-name collisions, to the extent that multiple versions of an assembly can coexist in the same process. Each file can contain multiple classes and multiple namespaces. A namespace can also be spread across several assemblies. An assembly can refer to another assembly. The equivalent of an assembly in Java is the .jar file. Classes within the same packages can be deployed in different .jar files, although it is not intuitive to do so.

3.4.11 What's Missing from Java?

From the discussion so far it should be clear that C# and Java, though touted as similar, are quite different in their approach to OOP. However, certain elements in C# do not have any equivalents in Java. Note that the following discussion does not cover all that is missing from Java but only some key concepts.

Structs

C# structs are lightweight constructs that inherit from the C# class System.Object. Unlike regular objects, structs are allocated on the stack. Structs can implement interfaces and can have the same kinds of members as classes. However, structs differ from classes in several important ways: Structs are value types rather than reference types, and inheritance is not supported for structs. Although structs can improve performance in places where a full class definition is not warranted, structs are, after all, value types, and you should take care when passing a large number of structs as method parameters; the latter are copied every time they are passed by value to the method, thereby consuming more memory. Structs are discussed in detail in Chapter 7.

Constants

A constant is a class member that represents a constant value: a value that can be computed at compile time. Constants are permitted to depend on other constants within the same program as long as there are no circular dependencies. Even though constants are considered static members, a constant declaration neither requires nor allows the modifier static. Here is an example:

public const int A = 1;

A const can have the following modifiers: new, public, protected, private, and internal. A constant declaration can be of the following types: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, an enum, or a reference type. Constants are typically used in expressions that in turn can be made up of other constants.

Properties

A property is a member that provides access to a characteristic of an object or a class. Examples of properties include the length of a string, the size of a font, the caption of a window, the name of a customer, and so on. Properties are a natural extension of fields. Unlike fields, properties do not denote storage locations. Instead, properties have accessors that specify the statements to be executed when their values are read or written.

Access modifiers on a property apply to both the get and the set block of a property. Here is a sample property:

public string Name {
    get {
      return name;
  }
    set {
      this.name = value;
  }
}
Console.WriteLine (person.Name);

Properties with only the get block are read-only properties. Properties can be static or nonstatic. They can be referenced through the name, and because the get and the set blocks are transparently called, properties simplify syntax.

Indexers

An indexer is a member that enables an object to be indexed in the same way as an array. Indexer declarations are similar to property declarations. The main differences are that indexers are nameless and include indexing parameters. The indexing parameters are provided between square brackets. A sample indexer definition is as follows:

public class Stack {
  public object this [int index] {
    get {
    }
    set {
    }
  }
}

This code enables the call Stack[i].

Enums

An enum type is a distinct type that declares a set of named constants. Each enum type has a corresponding integral type called the underlying type of the enum type. This underlying type must be able to represent all the enumerator values defined in the enumeration. An enum declaration that does not explicitly declare an underlying type has an underlying type of int. Enums can have the following modifiers: new, public, protected, internal, and private.

Delegates

Event programming is typical in GUI applications, in which specific user interactions fire off events. Associated with every event is a generator (source) and a receiver (target). The general paradigm of event programming is that the receiver registers itself with the generator. Upon registration, the generator adds a listener to an internal list of listeners it maintains. The generator can also remove a listener from this list. When an event is fired, the generator enumerates through this list of listeners and notifies them of the event. In Java, the generator, listener, and the event itself are all classes. In C#, however, the event is modeled after delegates.

C# delegates can be thought of as type-safe function pointers. Function pointers are references to actual functions. A delegate can also wrap a call to a series of functions that can be added or deleted dynamically. Because of the indirect nature of the function call, delegates are useful for simulating asynchronous method calls (for example, the System.Threading and System.IO namespaces). Delegates are also used in event-based programming in C#. Thus, event programming in C# is similar to the JavaBeans property change listener paradigm. Because you can add or delete functions to a delegate, which in turn points to multiple functions, delegates can conveniently simulate the JavaBeans property change listener paradigm.

Attributes

Attributes form the declarative elements of the C# language. C# allows programmers to invent new kinds of declarative information, attach this declarative information to various program entities, and retrieve this declarative information at runtime. Programs specify this additional declarative information by defining and using attributes. Custom attributes can be created and attached to program elements (class, methods, assemblies, etc.). You can create a custom attribute and attach it to your class so that you can keep track of who made what contribution to the class source.

In addition to the features we've discussed, Java lacks the following:

  • The ref and out method parameters

  • Boxing and unboxing of primitive types

  • Operator overloading

  • Explicit interface declaration

  • Runtime code generation

  • Support for pointer-based code (built into the API)

  • The foreach statement and support for strings in the switch statement

  • Deterministic object cleanup

  • Pass by reference

  • Variable-length method parameter lists

  • Versioning

To get a feel for a more comprehensive list of differences between C# and Java, you're encouraged to look at the summaries of the individual chapters. These have not been duplicated here for the sake of brevity.

3.4.12 What's Missing from C#?

Although C# has improved a lot upon Java, there are certain language features Java programmers will notice are missing from C#:

  • Checked exceptions

  • Typed collections

  • Anonymous inner classes

  • Dynamic class loading

  • Interfaces that contain fields

C# has its own slew of features that, as a Java programmer, you wish it had. One of the most glaring cutbacks is the lack of support for checked exceptions. Accounting for checked exceptions is as routine as declaring variables in Java, and in fact, many Java APIs enforce predictable execution paths by throwing checked exceptions instead of returning null or silently ignoring the exceptional condition. There are quite a few places in the C# API where a method returns null instead of throwing an exception. This is something Java programmers will have to be extremely careful about.

    [ directory ] Previous Section Next Section