Home > Articles > Programming > Windows Programming

.NET Framework and Language Enhancements in 2005

  • Print
  • + Share This
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.
This chapter is from the book

IN THIS CHAPTER

  • Shared .NET Language Additions
  • VB Language Enhancements
  • C# Language Enhancements
  • .NET Framework 2.0 Enhancements

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 group of .NET language enhancements includes the following key additions:

  • Generics
  • Nullable types
  • Partial types
  • Properties with mixed access levels
  • Ambiguous namespaces

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

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 first—especially if you start looking through code that contains strange angle brackets in the case of C# or the Of keyword for Visual Basic. The following sections define generics, explain their importance, and show you how to use them in your code.

Generics Defined

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 easiest example is that of creating a collection class that contains other objects. For our example, image you want to store a series of objects. You might do so by adding each object to an ArrayList. However, the compiler and runtime know only that you have some list of objects. The list could contain Order objects or Customer objects or both (or anything). The only way to know what is contained in the list is to write code to check for the type of the object in the list.

Of course, to get around this issue, you might write your own strongly typed lists. Although this approach is viable, it results in tedious code written over and over for each type you want to work with as a collection. The only real difference in the code is the type allowed in the list. In addition, you still have to do all the casting because the underlying list still simply contains types as System.Object.

Now imagine if you could write a single class that, when used, allows the user to define its type. You can then write one generic list class that, instead of containing types as System.Object, would contain objects as the type with which the class is defined. This allows a caller to the generic list to decide the list should be of type Orders or only contain Customers. This is precisely what generics afford us. Think of a generic class as a template for a class.

Generics come in two flavors: generic types and generic methods. Generic types are classes whose type is defined by the code that creates the class. A generic method is one that defines one or more generic type parameters. In this case, the generic parameter is used throughout the method but its type is defined only when the method is called. In addition, you can define constraints that control the creation of generics. In the coming sections, we'll look at all of these items.

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 System.Object. This presents a number of problems. First, there is no constraint or compiler checking on what goes into the object. The corollary is also true: You cannot know what you are getting out if you cannot constrain what goes in. Second, when you use the object, you must do type checking to verify its type and then do casting to cast it back to its original type. This, of course, comes with a performance penalty. Finally, if you use value types and store them in System.Object, then they get boxed. When you later retrieve this value type, it must be unboxed. Again, this adds unwanted code and unnecessary performance hits. Generics solve each of these issues. Let's look at how this is possible.

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 knows the actual type that is being used in place of the generic. Therefore, it can substitute the real type for the generic type. This process is called generic type instantiation.

The newly compiled, native type is now used by subsequent, similar requests. In fact, all reference types are able to share a single generic type instantiation because, natively, references are simply pointers with the same representation. Of course, if a new value type is used in the generic type instantiation, the runtime will jit a new copy of the generic type.

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.

As an example, suppose you are writing a class called Fields that works with name/value pairs similar to a Hashtable or Dictionary. You might declare the class as follows:

C#

public class Fields

VB

Public Class Fields

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 instantiated, you want it to be constrained to the types used to create the class. To add the type parameters to the class declaration, you would then write the following:

C#

public class Fields<keyType, valueType>

VB

Public Class Fields(Of keyType, valueType)

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:

C#

public void Add(keyType key, valueType value)

VB

Public Sub Add(key as keyType, value as valueType)

This indicates to the compiler that whatever types are used to create the class should also be used in this method. In fact, to consume the class, your code would first create an instance and pass type arguments to the instance. Type arguments are the types passed to type parameters. The following is an example:

C#

Fields<int, Field> myFields = new Fields<int, Field>();

VB

Dim myFields As New Fields(Of Integer, Field)

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:

C#

myFields.Add(1, new Field());

VB

myFields.Add(1, New Field())

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.

Creating Generic Methods

So far we've looked at generic type parameters. These type parameters end up defining variables with class-level scope. That is, the variable that defines the generic type is available throughout the entire class. As with any class you write, you may not need class-level scoping. Instead, it may be sufficient to define the elements passed to a given method. Generics are no different in this regard. You can define them at the class level (as we've shown) or at the method level (as we will see).

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:

C#

public void Save<instanceType>(instanceType type)

VB

Public Sub Save(Of instanceType)(ByVal type As instanceType)

To call this generic method, you must define the type passed to the method as part of the call to the method. Suppose the Save method defined in the preceding example is contained in a class called Field. Now suppose you have created an instance of Field and have stored a reference to it in the variable named myField. The following code shows how you might call the Save method passing the type argument to the method:

C#

myField.Save<CustomerOrder>(new CustomerOrder());

VB

myField.Save(Of CustomerOrder)(New CustomerOrder())

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 generally preferable to pass the type because it makes your code more readable and saves the compiler from having to look it up. Second, generic methods can be declared as static (or shared). Finally, you can define constraints on generic methods (and classes), as we will see in the next section.

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 overcome this perceived flaw.

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:

  • Derivation constraint—Allows you to indicate that the generic type must implement one or more specific interfaces or derive from a base class.
  • Default constructor constraint—Allows you to indicate that the generic type must expose a constructor without parameters.
  • Reference/value constraint—Allows you to indicate that a generic type parameter must either be a reference or a value type.

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

public class Fields<keyType, valueType> where keyType : ISort

VB Class Constraint

Public Class Fields(Of keyType, valueType As ISort)

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

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.

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.

Nullable Types

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 meant to free us from these issues. A nullable type is a special value type that can have a null assigned to it. This is unlike the value types we are accustomed to (int, bool, double, and so on); these are simply not initialized when declared. On the contrary, with nullable types, you can create integers, Booleans, doubles, and the like and assign them the value of null. You no longer have to guess (or code around) whether a variable has been set. This includes no longer having to provide a default value. Instead, you now can initialize or assign a variable to the value of null. You can now write code without default assumptions. In addition, nullable types also solve the issue of pushing and pulling nulls to and from the database. Let's look at how they work.

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 (System.Nullable). 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 demonstrate how nullable types are declared in both C# and VBL:

A C# Nullable Type Example

bool? hasChildren = null;

A VB Nullable Type Example

Dim hasChildren As Nullable(Of Boolean) = Nothing

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 turn that type to a nullable version. On the contrary, if you are coding in VB, you are required to be more explicit by defining the Nullable class as you would a similar generic. You can also use a similar syntax in C#, as in the following example:

System.Nullable<bool> hasChildren = null;

Working with Nullable Types

The generic System.Nullable structure contains two read-only properties: HasValue and Value. These properties allow you to work with nullable types efficiently. The HasValue property is a Boolean value that indicates whether a given nullable type has a value assigned to it. You can use this property in If statements to determine whether a given variable has been assigned. In addition, you can simply check the variable for null (C# only). The following provides an example of each:

C# HasValue Example

If (hasChildren.HasValue) {...}

VB HasValue Example

If hasChildren.HasValue Then

C# Checking the Variable for Null

if (hasChildren != null) {...}

VB Checking the Variable Value for Null

If hasChildren.Value <> Nothing Then

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

System.Nullable<bool> hasChildren = null;
Console.WriteLine(hasChildren);  //no exception is thrown
if (hasChildren != null) {
  Console.WriteLine(hasChildren.Value.ToString());
}
Console.WriteLine(hasChildren.Value); //throws InvalidOperationException

VB Value Property Example

Dim hasChildren As Nullable(Of Boolean) = Nothing
Console.WriteLine(hasChildren)  'no exception is thrown
If hasChildren.HasValue Then
  Console.WriteLine(hasChildren.Value.ToString())
End If
Console.WriteLine(hasChildren.Value)  'throws InvalidOperationException

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 potentially allows multiple team members to work on the same class without working on the same file (thus avoiding the related code-merge headaches). The other problem partial types solve is to further partition tool-generated code from that of the developer's. This keeps your code file clean (with only your work in it) and allows a tool to generate portions of the class behind the scenes. Visual Studio 2005 developers will immediately notice this when working with Windows forms, Web Service wrappers, ASP code-behind pages, and the like. If you've worked with these items in prior versions of .NET, you'll soon notice that when you're working in 2005, the generated code is now absent and the class that you write has been marked as partial.

Working with Partial Types

Partial types are declared as such using the keyword Partial. 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 Class, Structure, or Interface). Indicating a partial type tells the compiler to merge these items together upon compilation into a single .dll or .exe.

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 Partial keyword. You cannot, for instance, declare a class as Partial Public Person in one file and then declare that same class as Public Person in another file under the same namespace. Of course, to do so, you would add the Partial 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 implementations. For example, if you use the attribute System.SerializableAttribute on a partial type, the attribute will be applied to all portions of the type when merged and compiled. Finally, it's important to note that all partial types must be compiled into the same assembly (.dll or .exe). You cannot compile a partial type across assemblies.

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

private string _userId;
public string UserId {
  get { return _userId; }
  internal set { userId = value; }
}

VB Mixed Property Access Levels

Private _userId As String
Public Property UserId() As String
  Get
    Return _userId
  End Get
  Friend Set(ByVal value As String)
     _userId = value
  End Set
End Property

Ambiguous Namespaces

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:

global::System.Double myDouble;

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:

C#

using IoAlias = System.IO;

VB

Imports IoAlias = System.IO

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 colon operator:

C# new syntax

IoAlias::FileInfo file;

C# old syntax

IoAlias.FileInfo file;

VB

Dim file as IoAlias.FileInfo
  • + Share This
  • 🔖 Save To Your Account