| [ directory ] |
|
3.4 Language Constructs, Syntax, and the APIThis section compares the building blocks of C# and Java. We use the following categories:
3.4.1 Data TypesC# 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.
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 OperatorsTable 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.
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 StatementsTable 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) {
}
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 SupportBoth 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.
3.4.5 Common ObjectsThe 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.
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 ElementsThis section briefly compares some of the core language elements common to C# and Java. ClassesClasses 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.
ConstructorsConstructors 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. DestructorsC# 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 MembersClass 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. VariablesVariables 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. MethodsA typical Java or C# method declaration looks like this: attributes method-modifiers return-type method-name (formal parameter list)
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#:
The following method modifier combinations are not allowed in Java:
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. InterfacesLike 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 LibraryThe 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:
3.4.8 ThreadingIn 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:
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/OutputBoth 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 AssembliesIn 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. StructsC# 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. ConstantsA 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. PropertiesA 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. IndexersAn 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]. EnumsAn 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. DelegatesEvent 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. AttributesAttributes 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:
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#:
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 ] |
|