Understanding .NET (2nd Edition)

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 was once 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

Designed by Microsoft, C# first appeared with the release of Visual Studio .NET in 2002. Building on this initial version, Visual Studio 2005 implements C# 2.0. As with everything else in this book, the version of the language described here is the one provided in this 2005 release.

Visual Studio 2005 implements version 2.0 of the C# language

The most popular tool today for creating C# code is Microsoft's Visual Studio. 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. Given the strong support Visual Studio provides for building CLR-based applications in C#, however, 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

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. 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 2, 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 that 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 (known as fields in the jargon of the CTS), 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. Main must be declared as static, and although it's not shown here, it can take arguments passed in when the program is started. 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 2. 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

5 factorial: 120 Square root of 5: 2.2361

This example is unrealistically simple, but the goal is to give you a feeling for the general structure and style of C#. There's much more to the language, as described next.

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

C# Types

Each type defined by C# is built on an analogous CTS type provided by the CLR. Table 3-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

int i;

could have been replaced with

System.Int32 i;

Both work, and both produce exactly the same results.

Table 3-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

Class

class

Interface

interface

Delegate

delegate

C# types are built on CTS types

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

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

Note that the base class, if there is one, must come first in this list, followed by any interface names. C# classes can also be labeled as sealed or abstract, as defined in Chapter 2, and can be assigned a visibility of public or internal (which is the default). These translate into the CTS-defined visibilities public and assembly, respectively. All of this information is stored in the metadata for the class once it has been compiled.

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

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. Fields and methods were both illustrated in the simple example program shown earlier, but properties are important enough to deserve their own example.

A C# class can include fields, methods, and properties

Any field marked as public can be accessed directly by code in another class. But suppose the class in which this field is defined needs to control how that access happens. Maybe every assignment to this field should be checked against pre-defined limits, for example, and maybe every attempt to read this field should be verified in some way. One way to address this would be to mark the field as private, then create methods through which the field could be modified and read. Because this pattern is so common, however, C# provides properties as an easier way to accomplish the same thing. Here's a simple example:

class PriorityValue { private int pValue; public int Priority { get { return pValue; } set { if (value > 0 && value < 11) pValue = value; } } } class PropertyExample { static void Main() { PriorityValue p = new PriorityValue(); p.Priority = 8; System.Console.WriteLine("Priority: {0}", p.Priority); } }

The class PriorityValue declares the private field pValue followed by a property named Priority. You can tell that Priority is a property because it contains the two accessors get and set. All access to a property goes through the code contained in these accessors. Here, for example, an attempt to read the Priority property executes the get code, which just returns whatever is in pValue. An attempt to modify Priority executes the set code, which updates this property only if the new value is between 1 and 10. The keyword value contains whatever the calling code is trying to assign to this property.

A property forces all access to a value to use get and set methods

Why are properties an improvement over writing your own methods to get and set a field's value? Because rather than requiring explicit calls to get and set methods, code using a property views that property just as it would a fieldthe syntax is the same. This lets the calling code be simple while still letting the property control how it is read and modified. In fact, there's a strong argument that public fields should never be used. Properties are always a better choice.

Properties can be accessed just like fields

A class can implement one or more constructors, which are methods called when an instance of this class is created. Each class can also provide at most one destructor, which is actually the name C# uses for a finalizer, a concept described in Chapter 2. 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 in the parent must be declared with the keyword virtual, and the child class must label the new member with the keyword override. 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.

Classes can provide constructors, override methods in their parent, and redefine operators

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 inheriting 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

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.

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

Structures

The CTS doesn't explicitly define a structure type. Instead, C# structures are based on classes, and so like a class, a structure can implement interfaces, contain methods, fields, and properties, and more. Unlike classes, however, structures are value types (they inherit from System.ValueType) rather than reference types, which means they're allocated on the stack. Recall that value types are also prohibited from participating in inheritance, so unlike a class, a structure can't inherit from another type. 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 keyword class in its definition to struct. If this were done, things would be slightly different during execution, but the program's output would be unchanged.

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 2, they are reference types, which means they get allocated on the heap. Here's an example that declares a single-dimensional array of integers:

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.

Like CTS arrays, C# arrays are reference types

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

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# arrays can be multidimensional

C#'s array type is built on the core array support provided by the CLR. As mentioned in the previous chapter, 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

Delegates and Events

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 specific event occurs. You need some way to pass in the identity of this callback function at runtime. In 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 2, 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#:

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.

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

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 in a way that's significantly less risky than previous languages.

A delegate can be combined with other delegates

One very popular use of delegates is for handling events. In a GUI, for instance, the user's mouse clicks, key presses, and other inputs can be received as events, and events are also useful in other contexts. Because events are so common, C# and the .NET Framework provide special support for using delegates to handle events in a consistent way. The delegate an event uses is referred to as an event handler, but it's really just an ordinary delegate. The Framework defines two conventions for these event handlers, however:

  • An event handler doesn't return a value, i.e., its return type is void.

  • An event handler always takes two arguments. The first argument, identifying the source of the event, is by convention named sender and is of type System.Object (or in C#, just the type object, which is an alias for System.Object). This makes it easy for the receiver of an event to communicate back to whatever object raised the event by, say, invoking a method in that object. The second argument, containing any data that the source passes when it calls this event handler, is traditionally named e and is of type System.EventArgs or a type that inherits from System.EventArgs.

The .NET Framework and C# provide delegate-based support for events

Here's an example declaration for an event handler:

public delegate void MyEventHandler(object sender, MyEventArgs e);

In this example, the type MyEventArgs must be derived from System.EventArgs, extending that base type as needed to carry the event's data. For events that generate no event-specific information, the type used for the data passed into the event handler can be just System.EventArgs. (Even when no data is being passed, the convention for events requires that this parameter still appear in the call.) Because it's so common for an event not to have any event-specific data, the System namespace also includes a built-in type called EventHandler. This type is just a delegate with two arguments: an object followed by a System.EventArgs.

Delegates used for events follow specific conventions

Once an appropriate event handler (i.e., a delegate that follows the conventions just described) has been declared, it's possible to define an event using this delegate. Here's an example:

public event MyEventHandler MyEvent;

As this example shows, the declaration must include the event keyword and the type must be a delegate type.

C# provides the event keyword for declaring events

Given these basics, the clearest way to understand how events work is to look at an example. The simple illustration below contains three classes: EventSource, which defines the event; EventSink, which receives and responds to the event; and EventMain, which creates instances of these two classes, then actually raises the event. Here's the code:

public class EventSource { public event System.EventHandler EventX; public void RaiseEventX() { if (EventX != null) EventX(this, System.EventArgs.Empty); } } public class EventSink { public EventSink(EventSource es) { es.EventX += new System.EventHandler(ReceiveEvent); } public void ReceiveEvent(object sender, System.EventArgs e) { System.Console.WriteLine("EventX raised"); } } public class EventMain { public static void Main() { EventSource source = new EventSource(); EventSink sink = new EventSink(source); source.RaiseEventX(); } }

The event used in this example, EventX, is declared at the beginning of the EventSource class. Because the event has no associated data, this declaration uses the Framework's standard System.EventHandler class rather than declaring a custom event hander. Following this declaration is the RaiseEventX method. An event that has no event handlers registered for it will have the value of null, and so after making sure that EventX isn't nullthere's actually something to callthis method invokes the event. (System.EventArgs.Empty indicates that no data is being passed with the event.) Since an event is in fact a delegate, what's actually called is whatever method this delegate points to. And while this example doesn't show this, a delegate can point to multiple methods, so raising an event will cause all of the methods that have registered for it to be executed.

Events are initialized to null

The second class, EventSink, illustrates one approach to registering for and processing an event. The class's constructor, which like all constructors has the same name as the class itself and runs whenever an instance of the class is created, expects to be passed an instance of an EventSource object. It then registers an event handler for EventX using the += operator. In this simple example, EventSink's constructor registers the class's ReceiveEvent method. ReceiveEvent has the standard arguments used for events, and once it's called, this method will write a simple message to the console. (Although this example doesn't show it, event handlers can also be unregistered using the = operator.)

EventSinks can register for events using C#'s += operator

The final class, EventMain, contains the example's Main method. This method first creates an instance of EventSource, then creates an instance of EventSink, passing in the just-instantiated EventSource object. This causes EventSink's constructor to execute, registering the ReceiveEvent method with EventX in EventSource. In its final line, the Main method invokes the RaiseEventX method defined in EventSource. The result is the invocation of ReceiveEvent, and the program writes out

EventX raised

In the interest of simplicity, this example doesn't quite follow all of the conventions of events. Still, this example illustrates the fundamentals of how delegates are slightly enhanced by C# and some .NET Framework conventions to provide more direct support for events.

Direct support for events makes using this common paradigm easier

Generics

Suppose you wish to write a class that can work with data of various types. Maybe a particular application must work with information in pairs, for example, manipulating two values of the same type. One approach to doing this would be to define a different class for each kind of pair: one class for a pair of integers, another for a pair of strings, and so on. A more general solution would be to create a Pair class that stores two values of the type System.Object. Since every .NET type inherits from System.Object, an instance of this class could store integers, strings, and anything else. Yet System.Object can be anything, and so nothing would prevent an instance of this Pair class from storing one integer and one string rather than a pair of identically-typed values. For this and other reasons, working directly with System.Object types isn't an especially attractive solution.

What's really needed is a way to create an instance of a Pair type that allows specifying at creation time exactly what kind of information this Pair will contain, then enforces that specification. To address this, version 2.0 of C# in Visual Studio 2005 has added support for generic types, commonly known as just generics. When a generic type is defined, one or more of the types it uses is left unspecified. The actual type(s) that should be used are spelled out only when an instance of the generic type is created. Those types can be different for different instances of the same generic type.

For example, here's a simple illustration of defining and using a generic Pair class:

class Pair<T> { T element1, element2; public void SetPair(T first, T second) { element1 = first; element2 = second; } public T GetFirst() { return element1; } public T GetSecond() { return element2; } } class GenericsExample { static void Main() { Pair<int> i = new Pair<int>(); i.SetPair(42,48); System.Console.WriteLine("int Pair: {0} {1}", i.GetFirst(), i.GetSecond()); Pair<string> s = new Pair<string>(); s.SetPair("Carpe", "Diem"); System.Console.WriteLine( "string Pair: {0} {1}", s.GetFirst(), s.GetSecond()); } }

The definition of the Pair class uses T, wrapped in angle brackets in its first appearance, to represent the type of information that an instance of this type will contain. The class's fields and methods work with T just as if it were any other type, using it for parameters and return values. Yet what T actually isan integer, a string, or something elseisn't determined until an actual Pair instance is declared.

As this example's Main method shows, creating an instance of a generic type requires specifying exactly what type should be used for T. Here, the first Pair will contain two integers, and so the type int is supplied when it is created. That Pair instance is then set to contain two integers, and its contents are written out. The second instance of Pair, however, will contain two strings, and so the type string is supplied when it is created. This time, the Pair instance is set to contain two strings, which are also written out. The result of executing this example is:

int Pair: 42 48 string Pair: Carpe Diem

What's New in C# 2.0

Generics are perhaps the most important addition in version 2.0 of C#, but several other new aspects are also worth mentioning. Those additions include the following:

  • Partial types: Using the new term partial, the definition of a class, an interface, or a struct can be spread across two or more source files. One common example of where this is useful is when a tool, such as Visual Studio, generates code that gets added to by a developer. With partial types, there's no need for a developer to directly modify the code produced by the tool. Instead, the tool and the developer each create a partial type, and the two types are combined to create the final definition. One important example of where partial classes are used is ASP.NET, described in Chapter 5.

  • Nullable types: It's sometimes useful for a value to be set to an undefined state, often referred to as null. The most common example of this is in working with relational databases, where null is often a legitimate value. To support this idea, C# 2.0 allows an instance of any value type to be declared with its name followed by a question mark, such as

    int? x;

    A variable declared this way can take on any of its usual values. Unlike an ordinary int, however, it can also be explicitly set to a null value.

  • Anonymous methods: One way to pass code as a parameter is to explicitly declare that code inside a delegate. In C# 2.0, it's also possible to pass code directly as a parameterthere's no requirement that the code be wrapped inside a separately declared delegate. In effect, the code passed acts like a method, but since it has no name, it's referred to as an anonymous method.

Generics can be used with classes, structs, interfaces, delegates (and thus events), and methods, although they'll probably be most common with classes. They aren't appropriate for every application, but for some kinds of problems, generic types can help in creating the right solution.

C# Control Structures

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

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

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

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

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 relatively new language are not very new, so most of them 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#, howevertoo many to cover in detail in this short survey. This section provides brief looks at some of the more interesting additional aspects of the language.

Working with Namespaces

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

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

System.Console.WriteLine(...);

To lessen the amount of typing required, C# provides the using directive. 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 line

using System;

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

Console.WriteLine(...);

A program can also contain several using directives if necessary, as some of the examples later in this book will illustrate.

Using the namespace keyword, it's also possible to define your own namespaces directly in C#. Each namespace can contain one or more types or perhaps even other namespaces. The types in these namespaces can then be referenced either with fully qualified names or through appropriate using directives, just as with externally defined namespaces.

Handling Exceptions

Errors are an inescapable fact of life 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

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.

An exception can be raised when an error occurs

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

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 is raised, 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.

Exceptions can be handled using try/catch blocks

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

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.

Different exceptions can be handled differently

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 directives 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 5.) 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 GetCustomAttributes 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 2, 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 this kind of mixing is 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 explicitly setting an "unsafe" option for 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 2 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++ developers. This directive can't be used to define an arbitrary replacement string for a word, howeveryou 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

Perspective: 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. Before the arrival of .NET, I saw more than one project that chose the Java environment primarily because neither Visual Basic 6 (VB 6) nor C++ was seen as a good language for large-scale enterprise development.

The arrival of C# and a .NET-based version of VB has certainly shored up Microsoft's language 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 inheritancethese are all similar to Java. Yet C# also added features that weren't available in Java. C#'s native support for properties, for instance, built on the support in the CLR, reflects the VB influence on C#'s creators. Attributes, also a CLR-based feature, provide a measure of flexibility beyond what Java originally offered, 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, C# might not 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 both languages have a long future ahead of them.

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.

C# is an attractive language. It combines a clean, concise design with a modern feature set. Introducing a new development technology is hardthe world is littered with the carcasses of unsuccessful programming languagesyet Microsoft has clearly succeeded with C#. As one of the two most widely used .NET languages, it's now squarely in the mainstream of software development.

Visual Basic

Before the release of .NET, VB 6 was by a large margin the most popular programming language in the Windows world. The first .NET-based release of VB, dubbed Visual Basic .NET (VB .NET), brought enormous changes to this widely used tool. The version supported by Visual Studio 2005, officially called Visual Basic 2005, builds on this foundation. It's nowhere near as big a change as the move from VB 6 to VB .NET, but this new version does offer a number of interesting new features.

Like C#, VB is built on the Common Language Runtime, and so large parts of the language are effectively defined by the CLR. In fact, except for their syntax, C# and VB are largely the same language. Because both owe so much to the CLR and the .NET Framework class library, the functionality of the two is very similar.

Except for syntax, C# and VB are very similar

VB can be compiled using Visual Studio or vbc.exe, a command-line compiler supplied with the .NET Framework. Unlike C#, however, Microsoft has not submitted VB to a standards body. Accordingly, while the open source world or some other third party could still create a clone, the Microsoft tools are likely to be the only viable choices for working in this language for the foreseeable future.

Only Microsoft provides VB compilers today

A VB Example

The quickest way to get a feeling for VB is to see a simple example. The one that follows implements the same functionality as did the C# example shown earlier in this chapter. As you'll see, the differences from that example are largely cosmetic.

' A VB example Module DisplayValues Interface IMath Function Factorial(ByVal F As Integer) _ As Integer Function SquareRoot(ByVal S As Double) _ As Double End Interface Class Compute Implements IMath Function Factorial(ByVal F As Integer) _ As Integer Implements IMath.Factorial Dim I As Integer Dim Result As Integer = 1 For I = 2 To F Result = Result * I Next Return Result End Function Function SquareRoot(ByVal S As Double) _ As Double Implements IMath.SquareRoot Return System.Math.Sqrt(S) End Function End Class Sub Main() Dim C As Compute = New Compute() Dim V As Integer 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)) End Sub End Module

The example begins with a comment, indicated by the single quote that begins the line. Following the comment is an instance of the Module type that contains all of the code in this example. Module is a reference type, but it's not legal to create an instance of this type. Instead, its primary purpose is to provide a container for a group of VB classes, interfaces, and other types. In this case, the module contains an interface, a class, and a Sub Main procedure. It's also legal for a module to contain method definitions, variable declarations, and more that can be used throughout the module.

A Module provides a container for other VB types

The module's interface is named IMath, and as in the earlier C# example, it defines the methods (or in the argot of VB, the functions) Factorial and SquareRoot. Each takes a single parameter, and each is defined to be passed by value, which means a copy of the parameter is made within the function. (The trailing underscore is the line continuation character, indicating that the following line should be treated as though no line break were present.) Passing by value is the default, so the example would work just the same without the ByVal indications[1]

[1] Passing by reference was the default in VB 6, one example of how VB was changed to match the underlying semantics of the CLR.

Like C#, VB's default is to pass parameters by value

A VB class is an expression of a CTS class

The class Compute, which is the VB expression of a CTS class, implements the IMath interface. Unlike C#, each of the functions in this class must explicitly identity the interface method it implements. Apart from this, the functions are just as in the earlier C# example except that a VBstyle syntax is used. Note particularly that the call to System.Math.Sqrt is identical to its form in the C# example. C#, VB, and any other language built on the CLR can access services in the .NET Framework class library in much the same way.

Perspective: C# or VB?

Before .NET, the language choice facing Microsoft-oriented developers was simple. If you were a hard-core developer, deeply proud of your technical knowledge, you embraced C++ in all its thorny glory. Alternatively, if you were more interested in getting the job done than in fancy technology, and if that job wasn't too terribly complex or low level, you chose VB 6. Sure, the C++ guys abused you for your lack of linguistic savoir faire, but your code had a lot fewer obscure bugs.

This divide ended with the arrival of .NET. C# and VB are very nearly the same language. Except for relatively uncommon things such as writing unsafe code, they're equally powerful. Microsoft may change this in the future, making the feature sets of the two languages diverge significantly. Until this happens, however (if it ever does), the main issue in making the choice is personal preference, which is really another way of saying "syntax."

Developers get very attached to how their language looks. C-oriented people love curly braces, while VB developers feel at home with Dim statements. In the years since .NET's original 2002 release, both languages have become popular, and both have ardent fans. Microsoft generally treats them equally, too, and even the .NET Framework documentation is quite even-handed, providing examples in both languages. Neither will go away anytime soon, and so either one is a safe choice for developers and the organizations that pay them.

In spite of this, however, I believe that any developer who knows C# can (and should) acquire at least a reading knowledge of VB, and vice versa. The core semantics are nearly identical, and after all, this is the really hard part of learning a language. In fact, to illustrate the equality of these two languages, the examples in the following chapters of this book alternate more or less randomly between the two. In the world of .NET, you shouldn't think of yourself as a VB developer or a C# developer. Whichever language you choose, you will in fact be a .NET Framework developer.

This simple example ends with a Sub Main procedure, which is analogous to C#'s Main method. The application begins executing here. In this example, Sub Main creates an instance of the Compute class using the VB New operator (which will eventually be translated into the MSIL instruction newobj). It then declares an Integer variable and sets its value to 5.

Execution begins in the Sub Main Procedure

As in the C# example, this simple program's results are written out using the WriteLine method of the Console class. Because this method is part of the .NET Framework class library rather than any particular language, it looks exactly the same here as it did in the C# example. Not too surprisingly, then, the output of this simple program is

5 factorial: 120 Square root of 5: 2.2361

just as before.

To someone who knows VB 6, the .NET version of VB will look familiar. To someone who knows C#, this version of VB will act in a broadly familiar way since it's built on the same foundation. But the VB implemented in Visual Studio 2005 is not the same as either VB 6 or C#. The similarities can be very helpful in learning this new language, but they can also be misleading.

VB's similarities to VB 6 both help and hurt in learning this new language

VB Types

Like C#, the types defined by VB are built on the CTS types provided by the CLR. Table 3-2 shows most of these types and their VB equivalents.

Table 3-2. Some CTS Types and Their VB Equivalents

CTS

VB

Byte

Byte

Char

Char

Int16

Short

Int32

Integer

Int64

Long

UInt16

UShort

UInt32

UInteger

UInt64

ULong

Single

Single

Double

Double

Decimal

Decimal

Boolean

Boolean

Class

Class

Interface

Interface

Delegate

Delegate

Unlike C#, VB is not case sensitive. There are some fairly strong conventions, however, which are illustrated in the example shown earlier. For people coming to .NET from VB 6, this case insensitivity will seem entirely normal. It's one example of why both VB and C# exist, since the more a new environment has in common with the old one, the more likely people are to adopt it.

VB is not case sensitive

Classes

VB classes expose the behaviors of a CTS class using a VB-style syntax. Accordingly, VB classes can implement one or more interfaces, but they can inherit from at most one other class. In VB, a class Calculator that implements the interfaces IAlgebra and ITrig and inherits from the class MathBasics looks like this:

Class Calculator Inherits MathBasics Implements IAlgebra Implements ITrig . . . End Class

Note that, as in C#, the base class must precede the interfaces. Note also that any class this one inherits from might be written in VB or in C# or perhaps in some other CLR-based language. As long as the language follows the rules laid down in the CLR's Common Language Specification, cross-language inheritance is straightforward. Also, 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. This is allowed only if the member being overridden is declared with the keyword Overridable, analogous to C#'s keyword virtual.

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

VB classes can be labeled as NotInheritable or MustInherit, which means the same thing as sealed and abstract, respectively, the terms used by the CTS and C#. VB classes can also be assigned various accessibilities, such as Public and Friend, which largely map to visibilities defined by the CTS. A VB class can contain variables, methods, properties, events, and more, just as defined by the CTS. Each of these can have an access modifier specified, such as Public, Private, or Friend. A class can also contain one or more constructors that get called whenever an instance of this class is created. And new with the 2005 version, VB supports operator overloading, like C#.

VB supports operator overloading

VB classes can also have properties. Here's the C# property shown earlier, this time expressed in VB:

Module PropertyExample Class PriorityValue Private m_Value As Integer Public Property Priority() As Integer Get Return m_Value End Get Set(ByVal Value As Integer) If (Value > 0 And Value < 11) Then m_Value = Value End If End Set End Property End Class Sub Main() Dim P As PriorityValue = New PriorityValue() P.Priority = 8 System.Console.WriteLine("Priority: {0}", _ P.Priority) End Sub End Module

As in the C# example, a property relies on a private value within the class to contain its information. The property's Get and Set methods look much like that earlier example, too, with the syntax changes required for VB. And access to the property once again looks just like accessing a public field in a class, with the advantage that both reading and writing its value rely on developer-defined code.

Interfaces

Like a CTS interface, a VB interface can inherit directly from one or more other interfaces

Interfaces as defined by the CTS are a fairly simple concept. VB essentially just provides a VB-derived syntax for expressing what the CTS specifies. Along with the interface behavior shown earlier, CTS interfaces can inherit from one or more other interfaces. In VB, for example, defining an interface ITrig that inherits from the three interfaces, ISine, ICosine, and ITangent, would look like this:

Interface ITrig Inherits ISine Inherits ICosine Inherits ITangent ... End Interface

Perspective: Is Inheritance Really Worthwhile?

Inheritance is an essential part of object technology. Until .NET, VB didn't really support inheritance, and so (quite correctly) it was not viewed as an object-oriented language. VB now has inheritance, since it's built on the CLR, and so it is unquestionably truly object-oriented.

But is this a good thing? Microsoft certainly could have added inheritance to VB long ago, yet the language's keepers chose not to. Whenever I asked Microsoft why this was so, the answers revolved around two main points. First, inheritance can be tricky to understand and to get right. In a class hierarchy many levels deep, with some methods overridden and others overloaded, figuring out exactly what's going on isn't always easy. Given that the primary target audience for VB was not developers with formal backgrounds in computer science, it made sense to keep it simple.

The second point often made about why VB didn't have inheritance was that in many contexts, inheritance was not a good thing. This argument was made most strongly with COM, a technology that had no direct support for implementation inheritance. Inheritance binds a child class to its parent very closely, which means that a change in the parent can be catastrophic for the child. This "fragile base class" issue is especially problematic when the parent and child classes are written and maintained by completely separate organizations or when the parent's source isn't available to the creator of the child. In the component-oriented world of COM, this is a more than plausible argument.

So why did Microsoft change its mind about inheritance? Inheritance still can be problematic if changes in a parent class aren't communicated effectively to all developers who depend on that class, and it can also be complicated. The arguments Microsoft made are not incorrect. Yet the triumph of object technology is complete: Objects are everywhere. To create new languages in a completely new environmentthat is, to create the .NET Framework and the current Visual Studiowithout full support for inheritance would brand any organization as irretrievably retro. And the benefits of inheritance, especially those gained by providing a large set of reusable classes such as the .NET Framework class library, are huge. The pendulum has swung, and inheritance is now essential.

Structures

Structures in VB are much like structures in C#. Like a class, a structure can contain fields, members, and properties, implement interfaces, and more. Like a C# structure, a VB structure is a value type, which means that it can neither inherit from nor be inherited by another type. A simple employee structure might be defined in VB as follows:

Structure Employee Public Name As String Public Age As Integer End Structure

To keep the example simple, this structure contains only data members. As described earlier, however, VB structures are in fact nearly as powerful as classes.

VB structures can contain fields, implement methods, and more

Arrays

Like arrays in C# and other CLR-based languages, arrays in VB are reference types that inherit from the standard System.Array class. Accordingly, all of the methods and properties that class makes available are also usable with any VB array. Arrays in VB look much like arrays in earlier versions of VB. Perhaps the biggest difference is that the first member of a VB array is referenced as element zero, while in pre-.NET versions of this language, the first member was element one. The number of elements in an array is thus one greater than the number that appears in its declaration. For example, the following statement declares an array of eleven integers:

Dim Ages(10) As Integer

Unlike VB 6, array indexes in VB start at zero

Unlike C#, there's no need to create explicitly an instance of the array using New. It's also possible to declare an array with no explicit size and later use the ReDim statement to specify how big it will be. For example, this code

Dim Ages() As Integer ReDim Ages(10)

results in an array of eleven integers just as in the previous example. Note that the index for both of these arrays goes from 0 to 10, not 1 to 10.

VB also allows multidimensional arrays. For example, the statement

Dim Points(10,20) As Integer

creates a two-dimensional array of integers with 11 and 21 elements, respectively. Once again, both dimensions are zero-based, which means that the indexes go from 0 to 10 in the array's first dimension and 0 to 20 in the second dimension.

Delegates and Events

The idea of passing an explicit reference to a procedure or function and then calling that procedure or function was not something that the typical VB 6 programmer was accustomed to. Yet the CLR provides support for delegates, which allows exactly this. Why not make this support visible in today's VB? Even more important, why not make events easy to use?

VB's creators chose to do both of these things, allowing VB programmers to create callbacks and other event-oriented code easily. Here's an example, the same one shown earlier in C#, of creating and using a delegate in VB:

Module DelegatesExample Delegate Sub SDelegate(ByVal S As String) Sub CallDelegate(ByVal Write As SDelegate) System.Console.WriteLine("In CallDelegate") Write("A delegated hello") End Sub Sub WriteString(ByVal S As String) System.Console.WriteLine( _ "In WriteString: {0}", S) End Sub Sub Main() Dim Del As New SDelegate( _ AddressOf WriteString) CallDelegate(Del) End Sub End Module

Although it's written in VB, this code functions exactly like the C# example shown earlier in this chapter. Like that example, this one begins by defining SDelegate as a delegate type. As before, SDelegate objects can contain references only to methods that take a single String parameter. In the example's Sub Main method, a variable Del of type SDelegate is declared and then initialized to contain a reference to the WriteString subroutine. (A VB subroutine is a method that, unlike a function, returns no result.) Doing this requires using VB's AddressOf keyword before the subroutine's name. Sub Main then invokes CallDelegate, passing in Del as a parameter.

CallDelegate has an SDelegate parameter named Write. When Write is called, the method in the delegate that was passed into CallDelegate is actually invoked. In this example, that method is WriteString, so the code inside the WriteString procedure executes next. The output of this simple example is exactly the same as for the C# version shown earlier in this chapter:

In CallDelegate In WriteString: A delegated hello

Delegates are another example of the additional features VB has acquired from being rebuilt on the CLR. While this rethinking of the language certainly requires lots of learning from developers using it, the reward is a substantial set of features.

VB allows creating and using delegates

One idea that's not new to VB is direct language support for events. Unlike the pre-.NET versions of VB, events are now built on delegates. Still, using events in VB can be relatively straightforward, simpler even than using them in C#. Here's the events example shown earlier recast into VB:

Module EventsExample Public Class EventSource Public Event EventX() Sub RaiseEventX() RaiseEvent EventX() End Sub End Class Public Class EventSink Private WithEvents Source As EventSource Public Sub New(ByVal Es As EventSource) Me.Source = Es End Sub Public Sub ReceiveEvent() _ Handles Source.EventX System.Console.WriteLine("EventX raised") End Sub End Class Sub Main() Dim Source As EventSource = New EventSource() Dim Sink As EventSink = New EventSink(Source) Source.RaiseEventX() End Sub End Module

VB events rely on delegates

Like the earlier example, this one includes an EventSource class, an EventSink class, and a Main that creates and uses an instance of each class. As this illustration shows, however, it's possible to use events in VB without explicitly working with any delegate types. Instead, an event can be declared using just the Event keyword, as is done in the first line of the EventSource class. There's no requirement to reference either a system-defined delegate such as System.EventHandler or a custom delegate (although it's certainly possible to do this). Also, raising an event doesn't necessarily require explicitly conforming to the argument convention used in C#. Instead, as shown in EventSource's RaiseEventX method, the RaiseEvent keyword can be used. The VB compiler fills in everything else that's required.

Events can be declared with the keyword Event

The way that event handlers are attached to events can also be somewhat simpler than in C#. In this example's EventSink class, for instance, the WithEvents keyword indicates that the Source field can raise events. The definition for a method that handles an event can use the Handles keyword to indicate which event this method should receive. This is exactly what's done by EventSink's ReceiveEvent method. And while like the earlier C# example, this code attaches the event's source to its receiver in EventSink's constructor (the New method), the details are different. Here, the Source field in EventSink is assigned whatever instance of the EventSource class is passed in when the EventSink is created. Finally, the Main method does just the same things as before: it creates instances of the two classes, then invokes the method that will ultimately raise the event. As before, the program's output is

EventX raised

There are also other ways to work with events in VB. It's possible to explicitly declare events using delegates as in C#, for example, and to attach event handlers using the AddHandler keyword. However it's done, events (and the delegates they rely on) are an important part of application development, used by Windows Forms, ASP.NET, and other fundamental parts of the .NET Framework.

Events in VB can be simpler than in C#

Perspective: Has VB Become Too Hard?

Maybe. There have been lots of complaints about the changes, and certainly some VB 6 developers have been left behind. Microsoft historically targeted quite separate developer markets with VB and C++, yet the .NET Framework greatly blurred this distinction. VB and C# are functionally almost identical.

The .NET Framework is certainly simpler in many ways than the Windows DNA environment it replaced. But the Framework is also harder for a certain class of developers, especially those with no formal training in computer science. One reason for Microsoft's success in the developer market was the approachability of VB. The people who create software tools often forget that they're almost always much better software developers than the people who will use those tools. As a result, they tend to create tools that they themselves would like to use, powerful tools that are too complex for many of their potential customers.

The original creators of VB didn't make this mistake. Despite the opprobrium heaped on the language and its users by C++ developers, Microsoft kept a clear focus on the developer population and skill level they wished to target. This was a good decision, as VB was once the world's most widely used programming language.

And yet many VB developers wanted more. The .NET-based versions of VB certainly gave them more, but it also required all VB developers to step up a level in their technical knowledge. Still, the skills required to build the GUI-based client of a two-tier application, the original target for VB, are almost entirely unrelated to what's needed to build today's scalable, multitier, Web-accessible solutions. Given this, perhaps the original audience Microsoft targeted for VB, some of whom were just a step above power users, no longer has a role. With its complete object orientation and large set of more advanced features, VB is certainly too complex for many of them.

Yet building modern applications effectively was becoming more and more difficult with the old VB. Between a rock and a hard place, Microsoft chose to make this popular language both more powerful and more complex. Some developers have been very happy about this, but some haven't. Even when you have Microsoft's vast resources, you can't please everybody.

Generics

Just as with C#, the 2005 release of VB adds support for generic types. Here's the Pair example shown earlier expressed in VB:

Module GenericsExample Class pair(Of t) Dim element1, element2 As t Sub SetPair(ByVal first As t, ByVal second As t) element1 = first element2 = second End Sub Function GetFirst() As t Return element1 End Function Function GetSecond() As t Return element2 End Function End Class Sub Main() Dim i As New pair(Of Integer) i.SetPair(42, 48) System.Console.WriteLine( _ "int Pair: {0} {1}", _ i.GetFirst(), i.GetSecond()) Dim s As New pair(Of String) s.SetPair("Carpe", "Diem") System.Console.WriteLine( _ "string Pair: {0} {1}", _ s.GetFirst(), s.GetSecond()) End Sub End Module

The syntax is different from the C# version shown earlier, most obviously in how the generic class is defined:

Class pair(Of t)

rather than C#'s

class Pair<T>

These superficial differences aside, however, generics in VB function as they do in C#.

Like C#, the 2005 version of VB also supports partial types, including partial classes and more. Unlike C#, however, it does not provide nullable types or anonymous methods. As was true in their original incarnations, the 2005 versions of C# and VB are functionally almost identical while still retaining a few small differences.

Perspective: Are Generics Worth It?

There's probably no better indication of the distance the VB language has traveled from its humble origins than the addition of generics. Generics are similar to templates in C++, a feature that's sometimes cited as an example of that language's excessive complexity. Do generics belong in VB?

One answer is to realize that generics are optional. Developers writing new applications can avoid using generics if they find the concept confusing. The trouble with this perspective is that Microsoft itself has begun using generics in the new programming interfaces it makes available. Given this, VB developers might be forced to confront this concept whether they like it or not.

Another view is to argue that generics just aren't that hard. Once you get your mind around the idea, they can actually make code simpler and less error-prone. This is certainly true for some percentage of .NET developers, but it's just as certainly not true for many others. Especially for developers focused more on solving business problems than on technicalitiesthe traditional heart of the VB communitythe subtleties of generics might be a bridge too far.

Whatever the truth turns out to be, the addition of generics to VB makes very clear that, despite its name, the traditional simplicity of the Basic language is gone for good.

VB Control Structures

While the CLR says a lot about what a .NET Frameworkbased language's types should look like, it says essentially nothing about how that language's control structures should look. Accordingly, adapting VB to the CLR required making changes to VB's types, but the language's control structures are fairly standard. An If statement, for example, looks like this:

If (X > Y) Then P = True Else P = False End If

while a Select Case statement analogous to the C# switch shown earlier looks like this:

Select Case X Case 1 Y = 100 Case 2 Y = 200 Case Else Y = 300 End Select

As in the C# example, different values of x will cause y to be set to 100, 200, or 300. Although it's not shown here, the Case clauses can also specify a range rather than a single value.

VB's control structures will look familiar to most developers

The loop statements available in VB include a While loop, which ends when a specified Boolean condition is no longer true; a Do loop, which allows looping until a condition is no longer true or until some condition becomes true; and a For...Next loop, which was shown in the example earlier in this section. And like C#, VB includes a For Each statement, which allows iterating through all the elements in a value of a collection type.

VB includes a While loop, a Do loop, a For...Next loop, and a For Each loop

VB also includes a GoTo statement, which jumps to a labeled point in the program, a Continue statement which returns to the top of the loop it's contained in and starts the next iteration (new in the 2005 version of the language), and a few more choices. The innovation in the .NET Framework doesn't focus on language control structures (in fact, it's not easy to think of the last innovation in language control structures), and so VB doesn't offer much that's new in this area.

Other VB Features

The CLR provides many other features, as seen in the description of C# earlier in this chapter. With very few exceptions, the creators of VB chose to provide these features to developers working in this newest incarnation of VB. This section looks at how VB provides some more advanced features.

VB exposes most of the CLR's features

Working with Namespaces

Just as in C#, namespaces are an important part of writing applications in VB. As shown earlier in the VB example, access to classes in .NET Framework class library namespaces looks just the same in VB as in C#. Because the CTS is used throughout, methods, parameters, return values, and more are all defined in a common way. Yet how a VB program indicates which namespaces it will use is somewhat different from how it's done in C#. Commonly used namespaces can be identified for a module with the Imports statement. For example, preceding a module with

Imports System

would allow invoking the System.Console.WriteLine method with just

Console.WriteLine( . . .)

VB's Imports statement is analogous to C#'s using directive. Both allow developers to do less typing. And as in C#, VB also allows defining and using custom namespaces.

VB's Imports statement makes it easier to reference the contents of a namespace

Handling Exceptions

One of the greatest benefits of the CLR is that it provides a common way to handle exceptions across all .NET Framework languages. This common approach allows errors to be found in, say, a C# routine and then handled in code written in VB. The exact syntax for how these two languages work with exceptions is different, but the underlying behavior, specified by the CLR, is the same.

Like C#, VB uses Try and Catch to provide exception handling. Here's a VB example of handling the exception raised when a division by zero is attempted:

Try X = Y/Z Catch System.Console.WriteLine("Exception caught") End Try

Any code between the Try and Catch is monitored for exceptions. If no exception occurs, execution skips the Catch clause and continues with whatever follows End Try. If an exception occurs, the code in the Catch clause is executed, and execution continues with what follows End Try.

As in C#, try/catch blocks are used to handle exceptions in VB

As in C#, different Catch clauses can be created to handle different exceptions. A Catch clause can also contain a When clause with a Boolean condition. In this case, the exception will be caught only if that condition is true. Also like C#, VB allows defining your own exceptions and then raising them with the Throw statement. VB also has a Finally statement. As in C#, the code in a Finally block is executed whether or not an exception occurs.

VB offers essentially the same exception handling options as C#

Using Attributes

A VB program can contain attributes

Code written in VB is compiled into MSIL, so it must have metadata. Because it has metadata, it also has attributes. The designers of the language provided a VB-style syntax for specifying attributes, but the result is the same as for any CLR-based language: Extra information is placed in the metadata of some assembly. To repeat once again an example from earlier in this chapter, suppose the Factorial method shown in the complete VB example had been declared with the WebMethod attribute applied to it. This attribute instructs the .NET Framework to expose this method as a SOAP-callable Web service, as described in Chapter 7. Assuming the appropriate Imports statements were in place to identify the correct namespace for this attribute, the declaration would look like this in VB:

<WebMethod()> Public Function Factorial(ByVal F _ As Integer) As Integer Implements IMath.Factorial

This attribute is used by ASP.NET to indicate that a method should be exposed as a SOAP-callable Web service. Similarly, including the attribute

<assembly:AssemblyCompanyAttribute("QwickBank")>

in a VB file will set the value of an attribute stored in this assembly's manifest that identifies QwickBank as the company that created this assembly. VB developers can also create their own attributes by defining classes that inherit from System.Attribute and then have whatever information is defined for those attributes automatically copied into metadata. As in C# or another CLR-based language, custom attributes can be read using the GetCustomAttributes method defined by the System.Reflection namespace's Attribute class.

Attributes are just one more example of the tremendous semantic similarity of VB and C#. Which one a developer prefers will be largely an aesthetic decision.

The My Namespace

The 2005 version of VB provides an interesting addition that's not part of C#: the My namespace. The goal is to move VB just a little closer to its roots by making it easier for developers to do common but potentially complex things. Toward this end, the My namespace includes a number of objects that simplify life for VB developers. Some of those objects are:

  • My.Application: This object lets developers more easily access information about the current application. For example, the My.Application.CommandLineArgs property allows a VB developer to access any arguments supplied on the command line when the application was invoked, while the My.Application.ChangeCulture method allows modifying the culture (e.g., from United States English to French) used for formatting dates and other things.

  • My.User: This object provides access to the current user of an application. Accessing the My.User.Name property returns the name of the current user, for example, while the IsInRole method can be used to determine whether this user belongs to a particular role, such as Administrator.

  • My.Computer: This object provides access to various aspects of whatever machine the current application is running on. My.Computer contains a set of properties that return other objects for different kinds of access. Some examples include My.Computer.Audio for playing .wav files, My.Computer.Clock for accessing the current time, My.Computer.FileSystem for working with files and directories, My.Computer.Network for uploading and downloading files, and My.Computer.Registry for accessing the local machine's registry.

  • My.Settings: This object allows working with an application's settings, such as information for connecting to a database or user preferences.

Perspective: Why Provide All of These Languages?

Microsoft says that more than 20 languages have been ported to the CLR. Along with the languages shipped by Microsoft itself, .NET developers have plenty of options to choose from. Yet given the CLR's central role in defining these languages, they often have much in common. What's the real benefit of having multiple languages based on the CLR?

For Microsoft, there are two key advantages. First, the pre-.NET population of Windows developers was split into two primary language camps: C++ and VB. Microsoft needed to move both groups of developers forward, and both certainly have some attachment to their language. Although the semantics of the CLR (and of languages built on it such as C# and VB) are different from either C++ or VB 6, the fundamental look of these new languages will be familiar. If Microsoft chose to provide only, say, C#, it's a safe bet that developers who were wedded to VB 6 would be resistant to moving to .NET. Similarly, providing only a CLR-based language derived from VB 6 wouldn't make C++ developers very happy. People who write code get attached to the oddest things (curly braces, for example), and so providing both C# and a CLR-based version of VB is a good way to help the current Windows developer population move forward.

The second benefit in providing multiple languages is that it gives the .NET Framework something the competition doesn't have. One complaint about the Java world has been that it requires all developers always to use the same language. The .NET Framework's multilingual nature offered more choice, so it gave Microsoft something to tout over its competitors.

In fact, however, there are some real benefits to having just one language. Why add extra complexity, such as a different syntax for expressing the same behavior, when there's no clear benefit? Java's traditional one-language-all-the-time approach has the virtue of simplicity. Even in the .NET world, organizations would do well to avoid multilanguage projects if possible. It's true that code written in different CLR-based languages can interoperate with no problems, and that developers who know C# should have no trouble understanding VB (and vice versa). Still, having two or more separate development groups using distinct languages will complicate both the initial project and the maintenance effort that follows. It's worth avoiding if possible.

So far, the diverse set of languages that are officially available for the .NET Framework hasn't mattered much. Because of Microsoft's strong support, expressed most powerfully in Visual Studio, C# and VB are by far the most widely used choices for creating new CLR-based applications. The other languages might be interesting for universities, but for professional developers, Visual Studio and the languages it supports rule.

There's no inherent reason why the My namespace classes couldn't be made accessible to developers working in C# or other CLR-based languages. Given VB's historic orientation toward less technical developers, however, it isn't surprising that this simplifying set of classes shows up here first.

Категории