The whirlwind tour is over. We've covered the basics of installation, your first project, and the standard items you'll encounter when journeying out on your own. We hope you've found your bearings and can begin pushing onward.
Chapter 3. .NET Framework and Language Enhancements in 2005
The majority of this book focuses on unlocking the productivity promises of the Visual Studio IDE. However, we thought it important to also cover some of the recent advances in the .NET languages and the Framework. These items (the IDE, the languages, and the Framework) all ship from Microsoft in concert. Therefore, any discussion of the new IDE would be incomplete without some mention of the elements that have been bound to it.
This chapter covers the enhancements relative to both Visual Basic .NET and C#. In addition, it highlights some of the key advances made in the Framework. Our assumption is that a majority of readers have some base-level understanding of either VB or a C-based language prior to the current version, along with a decent grasp of the .NET Framework. Therefore, our approach should give you insight into those enhancements that make .NET 2.0 a big leap forward over prior versions.
Shared .NET Language Additions
The .NET languages pick up a number of enhancements as a result of updates made to the common language runtime (CLR). Although there are specific enhancements for both Visual Basic and C#, respectively, the big advancements made in 2005 apply to both languages. Therefore, we will cover them as a group and provide examples in both languages. This
We will cover each of these items in detail in the coming sections. Again, we provide examples in both C# and VB because these enhancements apply to both languages. We will cover the VB and C# language-specific enhancements later in the chapter.
Generics are undoubtedly the biggest addition to .NET in version 2.0. As such, no book would be complete without covering their ins and outs. Generics may seem daunting at firstespecially if you start looking through code that contains
The concept of generics is relatively straightforward. You need to develop an object (or define a parameter to a method), but you do not know the object's type when you write the code. Rather, you want to write the code generically and allow the caller to your code to determine the actual type of the object.
You could simply use the System.Object class to accomplish this. That is what we did prior to 2.0. However, imagine you also want to eliminate the need for boxing, runtime type checking, and explicit casting everywhere in your code. Now you can start to see the vision for generics.
The benefits of generics can best be seen through an example. The
Of course, to get around this issue, you might write your own strongly typed lists. Although this approach is
Now imagine if you could write a single class that, when used, allows the
Generics come in two flavors: generic types and generic
The Benefits of Generics
Now you should plainly see some of the benefits that generics provide. Without them, any class that is written to manage different types must use
How .NET Manages Generics
When you compile a generic type, you generate Microsoft Intermediate Language (MSIL) code and metadata (just like all the rest of your .NET code). Of course, for the generic type or method, the compiler emits MSIL that defines your use of generic types.
With all MSIL code, when it is first accessed, the just-in-time (JIT) compiler compiles the MSIL into native code. When the JIT compiler encounters a generic, it
The newly compiled, native type is now used by
This is how we get the benefits of generics both when we're writing our code and when it executes. Upon execution, all our code becomes native, strongly typed code. Now let's look at coding some generics.
Creating Generic Types
Generic types are classes that contain one or more elements whose type should be determined at instantiation (rather than during development). To define a generic type, you first declare a class and then define type parameters for the class. A type parameter is one that is passed to a class that defines the actual type for the generic. You can think of a type parameter as similar to method parameters. The big difference is that, instead of passing a value or a reference to an object, you are passing the type used by the generic.
Most generic types are written to manage collections of objects or linked lists. Generics are not, however, limited to just managing collections. Any class you write can use generics.
As an example, suppose you are writing a class called
that works with
Let's also suppose that the class can work with a variety of types for its keys and a variety of types for its values. You want to write the class generically to support multiple types. However, after the class is
In this case, keyType and valueType are type parameters that can be used in the rest of the class to reference the types that will be passed to the class. For example, you might then have an Add method in your class whose signature looks like the following:
This indicates to the compiler that whatever types are used to create the class should also be used in this method. In fact, to
In this case a new instance of the generic Fields class is created that must contain int (integer) value for its keys and Field instances for its values. Calling the Add method of the newly created Fields object would then look like this:
If you try to pass another type to either parameter, you will get a compiler error because the object becomes strongly typed at this point.
When you see generics used,
Creating Generic Methods
So far we've
Generic methods work well for common, utility-like functions that execute a common operation on a variety of similar types. You define a generic method by indicating the existence of one or more generic types following the method name. You can then refer to these generic types inside the method's parameter list, its return type, and of course, the method body. The following shows the syntax for defining a generic method:
To call this generic method, you must define the type passed to the method as part of the call to the method. Suppose the
method defined in the
We need to add a few notes on generic methods. First, you can often omit the type parameter when calling a generic method. The compiler can figure out the type based on the parameter passed to it. Therefore, the type parameter is optional when calling a generic method. However, it is
Getting Specific with Generics (Constraints)
When you first encounter generic methods, it can be easy to think of them as simple data storage devices. At first glance, they seem to have a huge flaw. This flaw can best be described with the question that might be gnawing at you, "Generics are great, but what if you want to call a method or property of a generic object whose type, by definition, you are unaware of?" This flaw seems to limit the use of generics. However, upon a closer look, you'll see that generic constraints allow you to
Generic constraints are just what they sound like: They allow you to define restrictions on the types that a caller can use when creating an instance of your generic class or calling one of your generic methods. Generic constraints have the following three variations:
Using a derivation constraint enables you to indicate one or more interfaces (or object types) that are allowed to be passed to the generic class. Doing so allows you to overcome the aforementioned flaw. For example, if in the Fields generic class defined previously you need to be able to call a method or property of the generic valueType (perhaps a property that aids in sorting the group of Fields ), you can now do so, provided that method or property is defined on the interface or base class constraint. The following provides an example of defining a derivation constraint on a generic class:
C# Class Constraint
VB Class Constraint
In the preceding example, the class named Fields , which defines the two generic types valueType and keyType , contains a constraint on keyType . The constraint is that keyType must implement an interface called ISort . This now allows the generic class Fields to use methods of ISort without casting.
You can define a derivation constraint for both generic classes and generic methods.
You can indicate any number of interfaces that the generic type must implement. However, you can indicate only a single base class from which the generic type can derive. You can, of course, pass to the generic type an object that itself inherits from this constraining base class.
If you override a generic method in a base class, you cannot add (or remove) constraints to the generic method. Only the constraints defined in the base class will apply to the overridden method.
Generic Collections Namespace
Now that you've seen how to create your own generic classes, it is important to note that the .NET Framework provides a number of generic classes for you to use in your applications. The namespace System.Collections.Generics defines a number of generic collection classes designed to allow you to work with groups of objects in a strongly typed manner. A generic collection is a collection class that allows a developer to specify the type that is contained in the collection when declaring the collection.
By default, Visual Studio adds a reference to the namespace System.Collections. Generics to all VB and C# code files.
The generic classes defined in this namespace are varied based on their usage. The classes include one called List designed for working with a simple list or array of objects. It also includes a SortedList , a LinkedList , a Queue , a Stack , and several Dictionary classes. These classes cover all the basics of working without strongly typed collection classes. In addition, the namespace also defines a number of interfaces that you can use when building your own generic collections.
Most of us have written applications in which we were forced to declare a variable and choose a default value prior to knowing what value that variable should contain. For instance, imagine you have a class called Person with a Boolean property called IsFemale . If you do not implicitly know a person's sex at object instantiation, you are forced to pick a default, or you must implement the property as a tri-state enumeration (or similar) with values Male, Female , and Unknown .
The latter can be cumbersome, especially if the value is stored as a Boolean in the database. There are similar examples. Imagine if you are writing a Test class with an integer value called Score . If you are unsure of the Score value, you end up initializing this variable to zero (0). This value, of course, does not represent a real score. You then must program around this fact by either tracking zero as a magic number or carrying another property like IsScoreSet .
These examples are further amplified by the fact that the databases we work with all understand that a value can be null (or not set). We are often unable to use this feature unless we write code to do translation during our insert and select transactions.
Nullable types in .NET 2.0 are
Declaring Nullable Types
Declaring a nullable type is very different between the C# and VB languages. However, both result in declaring the same nullable value type structure inside the .NET Framework (
). This generic structure is defined by the type that is used in its declaration. For example, if you are defining a nullable integer, the generic structure returns an integer version. The following code snippets
A C# Nullable Type Example
A VB Nullable Type Example
Notice that in the C# example, you can use the
type modifier to indicate that a base type should be treated as a nullable type. This is simply a shortcut. It allows developers to use the standard syntax for creating types but simply add a question mark to
System.Nullable<bool> hasChildren = null;
Only value types can be nullable. Therefore, it is not valid to create a nullable string or a developer-defined class. However, you can create nullable instances of structures because they are value types.
Working with Nullable Types
structure contains two read-only properties:
. These properties allow you to work with nullable types
C# HasValue Example
VB HasValue Example
C# Checking the Variable for Null
VB Checking the Variable Value for Null
The Value property simply returns the value contained by the Nullable structure. You can also access the value of the variable by calling the variable directly (without using the Value property). The distinction lies in that when HasValue is false, calls to the Value property will result in an exception being thrown. Whereas when you access the variable directly in this condition ( HasValue = false ), no exception is thrown. Therefore, it is important to know exactly the behavior you require and use these options correctly. The following provides an example of using the Value property:
C# Value Property Example
VB Value Property Example
In the preceding example, the call directly to hasChildren will not throw an exception. However, when you try to check the Value property when the variable is null, the Framework throws the InvalidOperationException .
Partial Types (Classes)
Partial types are simply a mechanism for defining a single class, struct, or interface across multiple code files. In fact, when your code is compiled, there is no such thing as a partial type. Rather, partial types exist only during development. The files that define a partial type are merged together into a singe class during compilation.
Partial types are meant to solve two problems. First, they allow developers to split large classes across multiple files. This
Working with Partial Types
Partial types are declared as such using the keyword
. This keyword is actually the same in both C# and VB. You can apply this keyword to classes, structures, and interfaces. If you do so, the keyword must be the first word on the declaration (before
). Indicating a partial type
When defining partial types, you must follow a few simple guidelines. First, all types with the same name in the same namespace must use the
keyword. You cannot, for instance, declare a class as
Partial Public Person
in one file and then declare that same class as
in another file under the same namespace. Of course, to do so, you would add the
keyword to the second declaration. Second, you must keep in mind that all modifiers of a partial type are merged together upon compilation. This includes class attributes, XML comments, and interface
Properties with Mixed Access Levels
In prior versions of .NET, you were able to indicate the access level (public, private, protected, internal) only of an entire property. However, often you might need to make the property read (get) public but control the write (set) internally. The only real solution to this problem using prior .NET versions was not to implement the property set. You would then create another internal method for setting the value of the property. It would make your coding easier to write and understand if you had fine-grained control over access modifiers of your properties.
.NET 2.0 gives you control of the access modifiers at both the set and get methods of a property. Therefore, you are free to mark your property as public but make the set private or protected. The following code provides an example:
C# Mixed Property Access Levels
VB Mixed Property Access Levels
On large projects, it is possible to easily run into namespace conflicts with each other and with the .NET Framework ( System namespace). Previously, these ambiguous references were not resolvable. Instead, you got an exception at compile time.
.NET 2.0 now allows developers to define a System namespace of their own without blocking access to the .NET version. For example, suppose you define a namespace called System and suddenly are unable to access the global version of System . In C# you would add the keyword global along with a namespace alias qualifier :: as in the following syntax:
In VB the syntax is similar but uses the keyword Global :
Dim myDouble As Global.System.Double
To further manage namespace conflict, you can still define an alias when using (or importing) a namespace. This alias can then be used to reference types within the namespace. For example, suppose you had a conflict with the System.IO namespace. You could define an alias upon import as follows:
You could then reference types by using the alias directly. Of course, Visual Studio still gives you complete IntelliSense on these items. The following provides an example of using the alias defined in the preceding example. Notice the new syntax that is possible in C# with the double
C# new syntax
C# old syntax