|
|||||
|
This document is meant as a guide for the students of COMP 410 to jump into C# development. As Rice computer science students, we are well versed in the Java programming language through classes such as COMP 212, 314, etc. In the following text I will highlight some of the initial pitfalls we developers may encounter, and I will demonstrate ways of overcoming the problems. I will also introduce developers to some of additional features of C# that can improve the quality and performance of our applications. Class DifferencesC# like Java is inherently an object-oriented programming language, and central to OOP is the definition of a class. How then does C# differ from Java in its definition and implementation of classes? Lets look at the differences: Class Declaration:Defining a class in C# is essentially the same as Java. The main difference one will notice in C# code is the way classes specify inheritance and interface implementation. Java makes use of the extends and implements keywords, whereas C# borrows the more compact but somewhat less readable colon representation. The following C# class, GMTTime, extends the abstract class Time and implements the ICloneable interface. Notice that the declaration of an abstract class is specified exactly the same as in Java. abstract class Time { ... } class GMTTime : Time, ICloneable { ...
} Class Construction:Constructors for C# classes are written almost the same as in Java. Although Java always calls either an explicit or implicit superclass constructor before executing other constructor code, C# makes this relationship explicit by requiring superclass calls (as well as calls to alternate constructors in the same class) to be written before the code block of the construction. Here are some sample constructors from the GMTTime class: GMTTime() : this(System.DateTime.Now) { ...
} GMTTime(long millis) : base(millis) { ...
} The default constructor in this example defers some processing to the second constructor, which initializes its superclass by calling the one-argument constructor. If no superclass constructor is explicitly called, C# calls the default constructor of the superclass. Notice that the superclass constructor is referenced with use of the keyword base this is used throughout the classs code to refer to the superclasss members rather than the Java keyword super. The use of this has not changed from Java from C#. For static initialization of a class, Java would run any block of code specified as static at the top-level of the class. C# formalizes the notion of a static initializer by requiring the block of code to be specified as follows: static GMTTime() { ... } This code block will be run before any instantiation and
after any static field initializers. Class Destruction:Java calls them finalizers, C++ calls them destructors, C# calls them ... destructors. To specify code that will execute once an object is no longer usable, declare the destructor as follows: ~GMTTime() { ... } Once the object is ready for garbage-collection, the destructor for the class will be called followed by the destructors for its superclasses. Do note that the destructor cannot be called explicitly at any time. If you need the ability to release resources before the object is ready for GC, check out the System.IDisposable interface in the .NET Framework. Method Inheritance:In Java all methods are virtual and methods in subclasses always override the matching methods of the superclass. In C# methods are non-virtual by default and can only be overridden if preceded by the virtual keyword. Furthermore, a subclass method matching a virtual method in a superclass will only override that method if the override keyword is explicitly present on the subclass method. Otherwise the subclass method behaves as if the new keyword is present thereby hiding the superclass method within the subclass. See the example below: class Time { virtual long GetTime() { ... } virtual int GetDate() { ... } String GetDateTimeString() { ... } // not
overrideable } class GMTTime : Time { override long GetTime() { ... } new int GetDate() { ... } // hides
Time.GetDate() } All calls to GetTime on any GMTTime instance will execute the
new method. By hiding the GetDate
method, all calls to GetDate on a GMTTime object will execute the new
method. However, casting a GMTTime
object to a Time object would allow execution of the original GetDate
method. new may also be used on any
other subclass member to cope with name duplication. Note that abstract methods are specified exactly as in Java and are implicitly virtual. Sealing virtual methods in C# is as simple as using the sealed keyword, which is analagous to Javas final keyword on methods.
Also note that method parameters can be passed-by-reference and
passed-by-result through the use of the ref and out keywords. These behaviors can be used to implement
the notorious swap procedure as well as provide for multiple output values. Variable-argument lists may also be
created through the use of the params keyword. These parameter-passing behaviors may
create unexpected overloads so be careful when specifying methods that differ
only in their use of these mechanisms. Member accessibility:C# utilizes the same three keywords as Java in addition to the new internal keyword. Public and private access are the same as in Java, but the meaning of protected access as well as the default access has changed. C# uses protected to describe the class and any of its subclasses, while internal describes all classes with the same program. Thus Javas protected corresponds to C#s protected internal, and Javas default access corresponds to C#s internal. C#s protected access has no Java equivalent. Default access depends on the type of class member so refer to the C# specification if curious. Other DifferencesNamespaces:Java introduced the notion of packages for organizing classes and resolving class name conflicts. C# utilizes namespaces in much the same way as packages, although it does not require the filesystem to mimick the namespace heirarchy. Essentially the package and import keywords have been replaced by namespace and using. The minor differences are that a namespace must be enclosed in a set of braces, and that the using statement maybe combined with an alias to abbreviate long namespace names while explicitly referencing separate namespaces: using Coll =
System.Collections; Exceptions:Java and C# differ very little in exception handling. The try-catch-finally blocks have exactly the same syntax and semantics, and all exceptions must be derived from the Exception class. However, unlike Javas requirement that exceptions not derived from RuntimeException or Error must be explicitly caught or thrown, all exceptions in C# behave like Java runtime exceptions. One additional note is the use of checked and unchecked statements to control whether arithmetic overflows raise an exception. Such a statement is written with the keyword followed by a block of code or a simple expression. The default behavior of overflows outside such a statement is specified as a compilation flag. Arrays:C# adds a whole new dimension to Java arrays. The declaration and operation of single dimension arrays remains the same, but for multi-dimension arrays C# has support for both Javas jagged arrays (arrays of arrays) as well as C++s true multidimensional (rectangular) arrays. Here is the syntax for each type of array, and an example of usage: int[][] javaArray = new
int[2][]; javaArray[0] = new int[3]; // rows may be
accessed/sized independently javaArray[1] = new int[5]; int[,] rectArray = new int[5,4]; // all elements
are stored contiguously rectArray[3,4] = 42; // access requires both
indices C# has also added a new foreach statement that makes array iteration incredibly easy and nearly mistake-free. In addition foreach can be used in conjunction with any data structure that implements the System.Collections.IEnumerable interface, such as lists and dictionarys. Here is the syntax for the foreach statement: int[] array; // initialized
somewhere int sum; foreach (int i in array) { sum += i; } Do note that the iteration variable is read-only and may not be assigned. New Language EnhancementsProperties:How many classes have you written where you declare all fields private and then turn around and write getter/setter methods for each field? This is all done in the name of encapsulation, but it ends up littering your code with hundreds of calls to these get/set methods. C# introduces the notion of properties to allow the class designer to specify and use the intrinsic editable properties of a class in a manner that is separate from the more complex methods or actions of a class. In a sense properties help publicly reestablish the field vs method paradigm for a class without sacrificing the benefits of encapsulation. This can go a long way toward enhancing code readability. Here is an example of using properties: class Circle { private int radius; public int Radius {
get { return radius; }
set { radius = value; } } public int Area {
get { return 3.14 * radius * radius;
} } } As you can see, each property is declared similarly to a field with the exception that it is followed by a block of code specifying how the value of the property is obtained (get) and how the value of the property can be modified (set). If either of these clauses is omitted, then the property will be made write-only or read-only, something that cannot be controlled for a field. Within the set clause, notice that there is an intrinsic variable named value that represents the value assigned by the user.
To use these properties declared above, we simply access them as we would
a field. If try to read a
write-only property or vice-versa, then the compiler will throw a syntax
error. Circle c = new Circle(); c.Radius =
3; int area = c.Area; Indexer Property / Operator Overloading:Additionally, there is a special property called the indexer (or item) property that is useful for objects that are collections of other objects, e.g. many of the collections classes in the .NET Framework. It provides the ability to specify actions to take upon reading or assigning an element of the object, i.e. through the use of [ ] operator. For more information on the indexer property or changing the behavior of other operators, please refer the C# specification. Events and Delegates:Many languages have struggled with the notion of event handling and, more generally, asynchronous callbacks. C and C++ utilizes function pointers, while Java went through a couple iterations ultimately settling on the Listener design pattern that requires a listener class. C# strives for a compromise by formalizing and typing function pointers and providing built-in support for adding and removing callbacks for an event similar to Javas many addXXXListener methods. To create a callback for an event in C#, one must first declare a delegate. A delegate essentially creates a name for a specific type/signature of a method. Once a delegate has been defined, any number of delegates may be created by passing a compatible static or instance method as the single argument to the delegate constructor. In effect this separates a single method away from its enclosing class, allowing the method to be invoked without a reference to the original class or instance.
This separation is a fairly powerful construct that can be utilized in
many ways, the most obvious of which is for event callbacks. To create an event for a specific
delegate callback, one must simplify declare the delegate as its type. When working within the .NET Framework,
the delegate type for events are restricted to a
delegate that accepts an object
sender argument followed by an EventArgs argument. Here is an example of using events
within the framework: delegate void Handler(object sender, EventArgs e); //
define the delegate class EventTest { public event Handler myevent; // declare the
event public EventTest() {
Handler h = new Handler(this.HandleEvent); //
construct delegate
myevent += h; // add delegate to event callback
queue
myevent += new
Handler(EventTest.StaticlyHandleEvent); // all-in-one } public void HandleEvent(object sender, EventArgs e) { ...
} public static void StaticlyHandleEvent(object sender,
EventArgs e) { ... } public static void
EventTest test = new EventTest();
if (test.myevent != null) // test for registered
callbacks
test.myevent(new EventArgs()); // trigger
event } } As you can see, once a proper delegate has been defined, then event creation, listener registration, and event triggering is a breeze. Of course you should subclass EventArgs if you need to pass extra information to the event handlers (you can use the predefined EventHandler delegate if you dont). In addition you can remove event handlers from an event with the -= operator and an identical or equivalent delegate. Value Types:
According to C# documentation everything is an object. That includes such primitive types as
integers, characters, and floating-point characters. However, allocating and deallocating all
such objects on the heap would significantly hamper the performance of a
program. As a result C# introduced
the notion of value types which are passed by value and stored on the
stack. Such classes are typically
small (since instances must be copied often) and resemble the behavior of
stack-allocated structs in C++. However, creating object instances for every integer in a program would cause a massive performance hit so C# only creates such instances when a value type is cast to a superclass (e.g. ValueType or object). These instances are created by boxing the value, and a subsequent downcast would result in the value being unboxed for general use. This boxing behavior results in the following pitfall:
int i = 3; bool b = ((object)i = = (object)i); // false,
since separate boxes are created As a final note on value types, it is possible to create new value types through the use of the struct keyword. Such objects will be passed by value, although they have no inheritance and the default constructor may not be overridden. Other than those restrictions fields, properties, and methods are specified and behave the same as other classes. Enumerations:One thing sorely missing from Java was the ability to create enumerations. This resulted in lots of static constants that were used to emulated such behavior. C# provides the capability to create enumerations as in C, C++ or Pascal, but it also has some extended capabilities such as underlying type specification and runtime detection of illegal values. Bit mask creation is also supported through the use of attributes (see below). Here is some sample enumeration code: enum Color : byte { red = -1, blue = -2, green =
-3 } Color c =
-2; if (Enum.IsDefined(Color, c)) { ...
} Attributes:Finally we come to the most radically new concept between C# and Java. Attributes provide a declarative element to the language that allows language implementors as well as class designers to modify behavior and collect extra information about a class. It is possible to create new attributes, but that is outside the scope of this quick and dirty document. Instead Id like to highlight some currently useful attributes. Attributes can be declared on almost any element of a C# program. Typical examples show attributes being specified at the class level, but methods, return types, and parameters can all have attributes. Here are some attributes that are currently in use:
There are hundreds more attributes specified in the .NET
Framework, and the list keeps growing.
Aspect-oriented programming is a powerful new paradigm, and I would
advise you to read through the literature to get a better handle on how to wield
such power. |