C

< BACK  NEXT >
[oR]

C#

As its name suggests, C# is a member of the C family of programming languages. Unlike C, C# is explicitly object-oriented. Unlike C++, however, which is the most widely used object-oriented language in this family, C# isn't fiendishly complicated. Instead, C# was designed to be easily approachable by anyone with a background in C++ or Java.

C# is an object-oriented language with a C-like syntax

The most popular tool today for creating C# code is Microsoft's Visual Studio.NET. It's not the only choice, however. Microsoft also provides a command-line compiler with the .NET Framework called csc.exe, and the open source world has also created a C# compiler. Visual Studio.NET provides a rich environment for building CLR-based applications in C#, however, so it's hard to imagine that other alternatives will attract a large share of developers.

Microsoft provides the dominant C# compilers but not the only ones

Standardizing C# and the CLR

Microsoft has submitted C# and a subset of the CLR called the Common Language Infrastructure (CLI) to the international standards body ECMA, where they are on track to become ECMA standards. Along with C#, the things that have been submitted for standardization include the syntax and semantics for metadata, MSIL (rechristened the Common Intermediate Language, or CIL), and parts of the .NET Framework class library. For more details on exactly what has been submitted and its current status, see http://msdn.microsoft.com/net/ecma.

Sun came close to doing something similar with its Java technology but backed away at the last minute. Will Microsoft's efforts be more successful? Sun resisted this step in large part because they were unwilling to give up control of Java. Control of Java is a valuable thing, and since Sun is a for-profit company, its reluctance to relinquish this control makes perfect sense. Microsoft is also a for-profit company. Will they really wait until ECMA has approved, say, an enhancement to C# before including it in their next release? And if they do, is this a good thing? Standards bodies aren't known for their speed.

I'd be surprised if Microsoft lets ECMA control the rate at which innovations appear in future releases of .NET technologies. Still, making C# and the CLI standards does give others a way to build them. Somewhat surprisingly, given its traditional antipathy toward Microsoft, the open source world has spawned various efforts to build parts of .NET. The most visible of these is the Mono project. (Mono means monkey in Spanish, which may be an oblique commentary on the Mono team's view of Microsoft.) Mono's ambitious goal is to implement at least a large part of what Microsoft has given to ECMA, including a C# compiler and the CLI and perhaps more. Mono's creators say that they were attracted to the CLR for technical reasons, which must please Microsoft. In fact, from Mono's perspective, the CLI is the specification of a system while .NET's CLR is just the Microsoft implementation of this specification. Mono is certainly an interesting undertaking; to learn more about it, see http://www.go-mono.com.

Microsoft itself, together with Corel, has announced plans to make an implementation of C# and the CLI available for BSD UNIX. As discussed in Chapter 1, Microsoft faces substantial credibility problems in porting the .NET Framework to non-Windows platforms. Still, it's early in the game, and anything is possible. Whatever happens, having public standards and an open source implementation for their core technologies will certainly be a new experience for Microsoft.

A C# Example

Like most programming languages, C# defines data types, control structures, and more. Unlike older languages, however, C# does this by building on the CLR. Understanding the CLR therefore takes one a long way toward understanding C#. To illustrate this, here's a simple C# example:

 // A C# example interface IMath {       int Factorial(int f);       double SquareRoot(double s); } class Compute : IMath {       public int Factorial(int f)       {              int i;              int result = 1;              for (i=2; i<=f; i++)                 result = result * i;       return result;     }     public double SquareRoot(double s)     {         return System.Math.Sqrt(s);     } } class DisplayValues {     static void Main()     {         Compute c = new Compute();         int v;         v = 5;         System.Console.WriteLine(             "{0}  factorial: {1}",             v, c.Factorial(v));         System.Console.WriteLine(             "Square root of {0}: {1:f4} ",             v, c.SquareRoot(v));     } } 

The program begins with a comment, indicated by two slashes, giving a brief description of the program's purpose. The body of the program consists of three types: an interface named IMath and the two classes Compute and DisplayValues. All C# programs consist of some number of types, the outermost of which must be classes, interfaces, structures, enums, or delegates. (Namespaces, discussed later, can also appear here.) All methods, fields, and other type members must belong to one of these types, which means that C# doesn't allow either global variables or global methods.

Every C# program is made up of one or more types

The IMath interface, which is a C# incarnation of the Common Type System (CTS) interface type described in Chapter 3, defines the methods Factorial and SquareRoot. Each of these methods takes one parameter and returns a numeric result. These parameters are passed by value, the default in C#. This means that changes made to the parameter's value within the method won't be seen by the caller once the method returns. Placing the keyword ref in front of a parameter causes a parameter to be passed by reference, so any changes made within the method will be reflected back to the caller.

A C# interface is an expression of a CTS interface

Each class in this example is also a C# incarnation of the underlying CTS type. C# classes can implement one or more interfaces, inherit from at most one other class, and do all of the other things defined for a CTS class. The first class shown here, Compute, implements the IMath interface, as indicated by the colon between Compute and IMath. Accordingly, this class must contain implementations for both of the interface's methods. The body of the Factorial method declares a pair of integer variables, initializes the second of them to 1, then uses a simple for loop to calculate the factorial of its parameter (and doesn't bother to check for overflow, which is admittedly bad programming practice). Compute's second method, SquareRoot, is even simpler. It relies on the .NET Framework class library, calling the Sqrt function provided by the Math class in the System namespace.

A C# class is an expression of a CTS class

The last type in this simple example, the class DisplayValues, contains only a single method named Main. Much like C and C++, a C# program begins executing with this method in whatever type it appears. Although it's not shown here, Main can take arguments passed in when the program is started, and it must be declared as static. In this example, Main returns void, which is C#'s way of saying that the method has no return value. The type void cannot be used for parameters as in C and C++, however. Instead, its only purpose is to indicate that a method returns no value.

Execution of a C# program begins with the method named Main

In this example, Main creates an instance of the Compute class using C#'s new operator. When this program is executed, new will be translated into the MSIL instruction newobj described in Chapter 3. Main next declares an int variable and sets its value to 5. This value is then passed as a parameter into calls to the Factorial and SquareRoot methods provided by the Compute instance. Factorial expects an int, which is exactly what's passed in this call, but SquareRoot expects a double. The int will automatically be converted into a double, since this conversion can be done with no loss of information. C# calls this an implicit conversion, distinguishing it from type conversions that are marked explicitly in the code.

The results are written out using the WriteLine method of the Console class, another standard part of the .NET Framework's System namespace. This method uses numbers that are wrapped in curly braces and that correspond to the variables to be output. Note that in the second call to WriteLine, the number in braces is followed by :f4 . This formatting directive means that the value should be written as a fixed-point number with four places to the right of the decimal. Accordingly, the output of this simple program is

The Console class's WriteLine method writes formatted output to the console

 5 factorial: 120 Square root of 5: 2.2361 

The goal of this example is to give you a feeling for the general structure and style of C#. There's much more to the language, as the next sections illustrate.

C# Types

Each type defined by C# is built on an analogous CTS type provided by the CLR. Table 4-1 shows most of the CTS types and their C# equivalents. As mentioned earlier in this book, all of these data types are defined in the System namespace. The C# equivalents shown here are in fact just shorthand synonyms for these alternative definitions. In the example just shown, for instance, the line

C# types are built on CTS types

 int i; 

could have been replaced with

 System.Int32 i; 

Both work, and both produce exactly the same results.

Table 4-1. Some CTS Types and Their C# Equivalents
CTS C#
Byte byte
Char char
Int16 short
Int32 int
Int64 long
UInt16 ushort
UInt32 uint
UInt64 ulong
Single float
Double double
Decimal decimal
Boolean bool
Structure struct
String string
Class class
Interface interface
Delegate delegate

Note that C# is case sensitive. Declaring a variable as Double rather than double will result in a compiler error. For people accustomed to languages derived from C, this will seem normal. To others, however, it might take a little getting used to.

Classes

C# classes expose the behaviors of a CTS class using a C-derived syntax. For example, CTS classes can implement one or more interfaces but inherit directly from at most one other class. A C# class Calculator that implements the interfaces IAlgebra and ITrig and inherits from the class MathBasics would be declared as

Like a CTS class, a C# class can inherit directly from only one other class

 class Calculator : MathBasics, IAlgebra, ITrig { ... } 

Note that the class must come first in this list. C# classes can also be labeled as sealed or abstract, as defined in Chapter 3, and can be assigned public or internal visibility. These translate into the CTS-defined visibilities public and assembly, respectively. The default is internal. All of this information is stored in the metadata for this class once it has been compiled.

A C# class can contain fields, methods, and properties, all of which are defined for any CTS class. Each of these has an accessibility, which is indicated in C# by an appropriate access modifier such as public or private. It can also contain one or more constructors, called when an instance of this class is created, and at most one destructor, which is actually the name C# uses for a finalizer, a concept described in Chapter 3. If the class inherits from another class, it can potentially override one or more of the type members, such as a method, in its parent. To do this, the member being overridden must be declared as virtual.

A C# class can include fields, methods, properties, constructors, destructors, and more

A class can also define overloaded operators. An overloaded operator is one that has been redefined to have a special meaning when used with instances of this class. For example, a class representing workgroups in an organization might redefine the + operator to mean combining two workgroups into one.

C# supports operator overloading

Interfaces

Interfaces are relatively simple things, and the basic C# syntax for describing an interface was shown in the earlier example. Not shown there was how C# expresses multiple interface inheritance, that is, one interface that inherits from more than one parent. If, for example, the interface ITrig inherits from the three interfaces, ISine, ICosine, and ITangent, it could be declared as

A C# interface can inherit directly from one or more other interfaces

 Interface ITrig: ISine, ICosine, ITangent { ... } 

ITrig will contain all the methods, properties, and other type members defined in its three parent interfaces as well as anything it defines on its own.

Structures

Reflecting their definition in the CTS, structures in C# are much like classes. They can contain methods, fields, and properties and implement interfaces and more. They are value types rather than reference types, however, which means they're allocated on the stack. Value types also are prohibited from participating in inheritance. Unlike a class, a structure can't inherit from another type, and it's also not possible to define a type that inherits from a structure.

C# structures are like slightly simplified C# classes

Here's a simple example of a C# structure:

 struct employee {       string name;       int age; } 

In this example, the structure contains only fields, much like a traditional C-style structure. Yet a structure can be much more complex. The Compute class shown earlier, for instance, could be converted to a structure, methods and all, by just changing the word class in its definition to struct. The program would function in just the same way.

Delegates

Passing a reference to a method is a reasonably common thing to do. For example, suppose you need to tell some chunk of code what method in your code should be called when a spe cific event occurs. You need some way to pass in the identity of this callback function at runtime. In C and C++, you can do this by passing the address of the method, that is, a pointer to the code you want to be called. In the type-safe world of the .NET Framework, however, passing raw addresses isn't allowed. Yet the problem doesn't go away. A type-safe way to pass a reference to a method is still useful.

Passing a reference to a method as a parameter is often useful

As described briefly in Chapter 3, the CTS defines the reference type delegate for this purpose. A delegate is an object that contains a reference to a method with a specific signature. Once it has been created and initialized, it can be passed as a parameter into some other method and then invoked. Here's a simple example of creating and using a delegate in C#:

A C# delegate provides a type-safe way to pass a reference to a method

 delegate void SDelegate(string s); class DelegateExample {     public static void Main()     {        SDelegate del = new SDelegate(WriteString);        CallDelegate(del);     }     public static void CallDelegate(SDelegate Write)     {        System.Console.WriteLine("In CallDelegate");        Write("A delegated hello");     }     public static void WriteString(string s)     {        System.Console.WriteLine("In WriteString:            {0}", s);     } } 

The example begins by defining SDelegate as a delegate type. This definition specifies that SDelegate objects can contain references only to methods that take a single string parameter. In the example's Main method, a variable del of type SDelegate is declared and then initialized to contain a reference to the WriteString method. This method is defined later in the class, and as required, has a single parameter of type string. Main then invokes the CallDelegate method, passing in del as a parameter.

CallDelegate is defined to take an SDelegate as its parameter. In other words, what gets passed to this method is a delegate object that contains the address of some method. Because it's an SDelegate, that method must have a single parameter of type string. Inside CallDelegate, the method identified by the passed-in parameter is referred to as Write, and after printing a simple message, CallDelegate invokes this Write method. Because Write is actually a delegate, however, what really gets called is the method this delegate references, WriteString. The output of this simple example is

 In CallDelegate In WriteString: A delegated hello 

Note that the CallDelegate method executes first, followed by WriteString.

Delegates can be significantly more complicated than this. They can be combined, for example, so that calling a single delegate results in calls to the two or more other delegates it contains. Yet even simple delegates can be useful. By providing a type-safe way to pass a reference to a method, they offer this important feature of C and C++ in a much less risky way.

A delegate can be combined with other delegates

Arrays

As in other languages, C# arrays are ordered groups of elements of the same type. Unlike many other languages, however, C# arrays are objects. In fact, as described in Chapter 3, they are reference types, which means they get allocated on the heap. Here's an example that declares a single-dimensional array of integers:

Like CTS arrays, C# arrays are reference types

 int[] ages; 

Since ages is an object, no instance exists until one is explicitly created. This can be done with

 ages = new int[10]; 

which allocates space for ten integers on the heap. As this example shows, a C# array has no fixed size until an instance of that array type is created. It's also possible to both declare and create an array instance in a single statement, such as

 int[] ages = new int[10]; 

Arrays of any type can be declared, but exactly how an array gets allocated depends on whether it's an array of value types or reference types. The example just shown allocates space for ten integers on the heap, while

 string[] names = new string[10]; 

allocates space for ten references to strings on the heap. An array of value types, such as ints, actually contains the values, but an array of reference types, such as the strings in this example, contains only references to values.

Arrays can also have multiple dimensions. For example, the statement

C# arrays can be multidimensional

 int[,] points = new int[10,20]; 

creates a two-dimensional array of integers. The first dimension has 10 elements, while the second has 20. Regardless of the number of dimensions in an array, however, the lower bound of each one is always zero.

C#'s array type is built on the core array support provided by the CLR. Recall from the previous chapter that all CLR-based arrays, including all C# arrays, inherit from System.Array. This base type provides various methods and properties that can be accessed on any instance of an array type. For example, the GetLength method can be used to determine the number of elements in a particular dimension of an array, while the CopyTo method can be used to copy all of the elements in a one-dimensional array to another one-dimensional array.

Standard methods and properties can be accessed on all C# arrays

C# Control Structures

C# provides the traditional set of control structures. Among the most commonly used of these is the if statement, which looks like this:

The control structures in C# are typical of a modern high-level language

 if (x > y)    p = true; else    p = false; 

Note that the condition for the if must be a value of type bool. It can't be an integer, as in C and C++.

C# also has a switch statement. Here's an example:

 switch (x) {     case 1:        y = 100;        break;     case 2:        y = 200;        break;     default:        y = 300;        break; } 

Depending on the value of x, y will be set to 100, 200, or 300. The break statements cause control to jump to whatever statement follows this switch. Unlike C and C++, these (or similar) statements are mandatory in C#, even for the default case. Omitting them will produce a compiler error.

C# also includes various kinds of loops. In a while loop, the condition must evaluate to a bool rather than an integer value, which again is different from C and C++. There's also a do/while combination that puts the test at the bottom rather than at the top and a for loop, which was illustrated in the earlier example. Finally, C# includes a foreach statement, which allows iterating through all the elements in a value of a collection type. There are various ways a type can qualify as a collection type, the most straightforward of which is to implement the standard interface System.IEnumerable. A common example of a collection type is an array, and so one use of a foreach loop is to examine or manipulate each element in an array.

C# includes while, do/while, for, and foreach loops

C# also includes a goto statement, which jumps to a particular labeled point in the program, and a continue statement, which immediately returns to the top of whatever loop it's contained in and starts the next iteration. In general, the control structures in this new language are not very new, so they will be familiar to anybody who knows another high-level language.

Other C# Features

The fundamentals of a programming language are in its types and control structures. There are many more interesting things in C#, however too many to cover in detail in this short survey. This section provides brief looks at some of the more interesting additional aspects of this new language.

Working with Namespaces

Because the underlying class libraries are so fundamental, namespaces are a critical part of programming with the .NET Framework. One way to invoke a method in the class libraries is by giving its fully qualified name. In the example shown earlier, for instance, the WriteLine method was invoked with

C#'s using statement makes it easier to reference the contents of a namespace

 System.Console.WriteLine(...); 

To lessen the amount of typing required, C# provides the using statement. This allows the contents of a namespace to be referenced with shorter names. It's common, for example, to start each C# program with the statement

 using System; 

If the example shown earlier had included this line, the WriteLine method could have been invoked with just

 Console.WriteLine(...); 

A program can also contain several using statements if necessary, as some of the examples later in this book will illustrate. It's also possible to define your own namespaces directly in C# containing types or even other namespaces. The types they contain can then also be referenced either with fully qualified names or through appropriate using statements.

Handling Exceptions

Errors are a fact of life, at least for developers. In the .NET Framework, errors that occur at runtime are handled in a consistent way through exceptions. As in so much else, C# provides a syntax for working with exceptions, but the fundamental mechanisms are embedded in the CLR itself. This not only provides a consistent approach to error handling for all C# developers, but also means that all CLR-based languages will deal with this potentially tricky area in the same way. Errors can even be propagated across language boundaries as long as those languages are built on the .NET Framework.

Exceptions provide a consistent way to handle errors across all CLR-based languages

An exception is an object that represents some unusual event, such as an error. The .NET Framework defines a large set of exceptions, and it's also possible to create custom exceptions. An exception is automatically raised by the runtime when errors occur. For example, in the code fragment

An exception can be raised when an error occurs

 x = y/z; 

what happens if z is zero? The answer is that the CLR raises the System.DivideByZeroException. If no exception handling is being used, the program will terminate.

C# makes it possible to catch exceptions, however, using try/catch blocks. The code above can be changed to look like this:

Exceptions can be handled using try/catch blocks

 try {     x = y/z; } catch {     System.Console.WriteLine("Exception caught"); } 

The code within the braces of the try statement will now be monitored for exceptions. If none occurs, execution will skip the catch statement and continue. If an exception occurs, however, the code in the catch statement will be executed, in this case printing out a warning, and execution will continue with whatever statement follows the catch.

It's also possible to have different catch statements for different exceptions and to learn exactly which exception occurred. Here's another example:

Different exceptions can be handled differently

 try {     x = y/z; } catch (System.DivideByZeroException) {     System.Console.WriteLine("z is zero"); } catch (System.Exception e) {     System.Console.WriteLine("Exception: {0} ",         e.Message); } 

In this case, if no exceptions occur, x will be assigned the value of y divided by z, and the code in both catch statements will be skipped. If z is zero, however, the first catch statement will be executed, printing a message to this effect. Execution will then skip the next catch statement and continue with whatever follows this try/catch block. If any other exception occurs, the second catch statement will be executed. This statement declares an object e of type System.Exception and then accesses this object's Message property to retrieve a printable string indicating what exception has occurred.

Since CLR-based languages such as C# use exceptions consistently for error handling, why not define your own exceptions for handling your own errors? This can be done by defining a class that inherits from System.Exception and then using the throw statement to raise this custom exception. These exceptions can be caught with a try/catch block, just like those defined by the system.

Custom exceptions can also be defined

Although it's not shown here, it's also possible to end a try/catch block with a finally statement. The code in this statement gets executed whether or not an exception occurs. This option is useful when some final cleanup must take place no matter what happens.

Using Attributes

Once it's compiled, every C# type has associated metadata stored with it in the same file. Most of this metadata describes the type itself. As described in the previous chapter, however, metadata can also include attributes specified with this type. Given that the CLR provides a way to store attributes, it follows that C# must have some way to define attributes and their values. As described later in this book, attributes are used extensively by the .NET Framework class library. They can be applied to classes, interfaces, structures, methods, fields, parameters, and more. It's even possible to specify attributes that are applied to an entire assembly.

A C# program can contain attributes

For example, suppose the Factorial method shown earlier had been declared with the WebMethod attribute applied to it. Assuming the appropriate using statements were in place to identify the correct namespace for this attribute, the declaration would look like this in C#:

 [WebMethod] public int Factorial(int f) {...} 

This attribute is used by ASP.NET, part of the .NET Framework class library, to indicate that a method should be exposed as a SOAP-callable Web service. (For more on how this attribute is used, see Chapter 7.) Similarly, including the attribute

 [assembly:AssemblyCompanyAttribute("QwickBank")] 

in a C# file will set the value of an assembly-wide attribute, one that gets stored in the assembly's manifest, containing the name of the company creating this assembly. This example also shows how attributes can have parameters, allowing their user to specify particular values for the attribute.

Developers can also create their own attributes. For example, you might wish to define an attribute that can be used to identify the date a particular C# type was modified. To do this, you can define a class that inherits from System.Attribute, then define the information you'd like that class to contain, such as a date. You can then apply this new attribute to types in your program and have the information it includes be automatically placed into the metadata for those types. Once they've been created, custom attributes can be read using the GetCustomAt-tributes method defined by the Attribute class, part of the System.Reflection namespace in the .NET Framework class library. Whether standard or custom, however, attributes are a commonly used feature in CLR-based software.

Custom attributes can also be defined

Writing Unsafe Code

C# normally relies on the CLR for memory management. When an instance of a reference type is no longer in use, for example, the CLR's garbage collector will eventually free the memory occupied by that type. As described in Chapter 3, the garbage collection process also rearranges the elements that are on the managed heap and currently in use, compacting them to free more space.

C# developers typically rely on the CLR's garbage collection for memory management

What would happen if traditional C/C++ pointers were used in this environment? A pointer contains a direct memory address, so a pointer into the managed heap would reference a specific location in the heap's memory. When the garbage collector rearranged the contents of the heap to create more free space, whatever the pointer pointed to could change. Blindly mixing pointers and garbage collection is a recipe for disaster.

Pointers and garbage collection don't mix well

Yet it's sometimes necessary. For example, suppose you need to call existing non-CLR-based code, such as the underlying operating system, and the call includes a structure with embedded pointers. Or perhaps a particular section of an application is so performance critical that you can't rely on the garbage collector to manage memory for you. For situations like these, C# provides the ability to use pointers in what's known as unsafe code.

C# allows creating unsafe code that uses pointers

Unsafe code can use pointers, with all of the attendant benefits and pitfalls pointers entail. To make this unsafe activity as safe as possible, however, C# requires that all code that does this be explicitly marked with the keyword unsafe. Within an unsafe method, the fixed statement can be used to lock one or more values of a reference type in place on the managed heap. (This is sometimes called pinning a value.) Here's a simple example:

 class Risky {     unsafe public void PrintChars()     {         char[] charList = new char[2];         charList[0] = 'A';         charList[1] = 'B';         System.Console.WriteLine("{0} {1}",             charList[0], charList[1]);         fixed (char* f = charList)         {             charList[0] = *(f+1);         }         System.Console.WriteLine("{0} {1}",             charList[0], charList[1]);     } } class DisplayValues {     static void Main()     {         Risky r = new Risky();         r.PrintChars();     } } 

The PrintChars method in the class Risky is marked with the keyword unsafe. This method declares the small character array charList and then sets the two elements in this array to A and B, respectively. The first call to WriteLine produces

 A  B 

just as you'd expect. The fixed statement then declares a character pointer f and initializes it to contain the address of the charList array. Within the fixed statement's body, the first element of this array is assigned the value at address f+1. (The asterisk in front of the expression means return what's at this address. ) When WriteLine is called again, the output is

 B  B 

The value that is one beyond the start of the array, the character B, has been assigned to the array's first position.

This example does nothing useful, of course. Its intent is to make clear that C# does allow declaring pointers, performing pointer arithmetic, and more, as long as those statements are within areas clearly marked as unsafe. The language's creators really want you to be sure about doing this, so compiling any unsafe code requires specifying the /unsafe option to the C# compiler. Also, unsafe code can't be verified for type safety, which means that the CLR's built-in code access security features described in Chapter 3 can't be used. Unsafe code can be run in only a fully trusted environment, which makes it generally unsuitable for software that will be downloaded from the Internet. Still, there are cases when unsafe code is the right solution to a difficult problem.

Unsafe code has limitations

Preprocessor Directives

Unlike C and C++, C# has no preprocessor. Instead, the compiler has built-in support for the most useful features of a preprocessor. For example, C#'s preprocessor directives include #define, a familiar term to C and C++ developers. This directive can't be used to define an arbitrary replacement string for a word, however you can't define macros. Instead, #define is used to define only a symbol. That symbol can then be used together with the directive #if to provide conditional compilation. For example, in the code fragment

 #define DEBUG #if DEBUG         // code compiled if DEBUG is defined #else         //code compiled if DEBUG is not defined #endif 

DEBUG is defined, so the C# compiler would process the code between the #if and #else directives. If DEBUG were undefined, something that's accomplished using the preprocessor directive #undef, the compiler would process the code between the #else and #endif directives.

Is C# Just a Copy of Java?

C# certainly does look a lot like Java. Given the additional similarities between the CLR and the Java virtual machine, it's hard to believe that Microsoft wasn't at least somewhat inspired by Java's success. By uniting C-style syntax with objects in a more approachable fashion than C++, Java's creators found the sweet spot for a large population of developers. I have seen projects that chose the Java environment rather than Microsoft technologies primarily because, unlike Java, neither Visual Basic 6 nor C++ was seen as a good language for large-scale enterprise development.

The arrival of C# and Visual Basic.NET will surely shore up Microsoft's technology against the Java camp. The quality of the programming language is no longer an issue. Yet this once again begs the question: Isn't C# like Java?

In many ways, the answer is yes. The core semantics of the CLR are very Java-esque. Being deeply object-oriented, providing direct support for interfaces, allowing multiple interface inheritance but only single implementation inheritance these are all similar to Java. Yet C# also adds features that aren't available in Java. C#'s native support for properties, for instance, built on the support in the CLR, reflects the Visual Basic influence on C#'s creators. Attributes, also a CLR-based feature, provide a measure of flexibility beyond what Java offers, as does the ability to write unsafe code. Fundamentally, C# is an expression of the CLR's semantics in a C-derived syntax. Since those semantics are much like Java, C# is necessarily much like Java, too. But it's not the same language.

Is C# a better language than Java? There's no way to answer this question objectively, and it wouldn't matter if there were. Choosing a development platform based solely on the programming language is like buying a car because you like the radio. You can do it, but you'll be much happier if your decision takes into account the complete package.

If Sun had allowed Microsoft to modify Java a bit, my guess is that C# wouldn't exist today. For understandable reasons, however, Sun resisted Microsoft's attempts to customize Java for the Windows world. The result is two quite similar languages, each targeting a different development environment. Competition is good, and I'm confident that both languages will be in wide use five years from now.

C# is an attractive language. It combines a clean, concise design with a modern feature set. Although the world is littered with the carcasses of unsuccessful programming languages, C# isn't likely to join them. With Microsoft pushing it and its own quality pulling it, C# looks destined for a bright future.

< BACK  NEXT >


Understanding. NET. A Tutorial and Analysis
Understanding .NET: A Tutorial and Analysis (Independent Technology Guides)
ISBN: 0201741628
EAN: 2147483647
Year: 2002
Pages: 60

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net