In-Depth

Take a Generics Approach

Improve code reusability, reliability, flexibility, and performance with generics, one of Visual Studio .NET 2005's key enhancements.

Microsoft's addition of generics to Visual Studio 2005 (code-named Whidbey) makes a new form of polymorphism available in Visual Basic .NET 2.0, C# 2.0, and the Common Language Runtime (CLR). Generics lead the list of .NET Framework enhancements, helping you achieve greater flexibility, reusability, reliability, performance, and type safety.

Of course, C# is type-safe already. C# code goes only to the memory locations where it's welcome, and it does so politely. The C# and VB.NET language compilers produce type-safe code at all times, verifying this during Just In Time (JIT) compilation. Type safety in general lets a programming language trap all possible errors at compile time rather than at run time. This keeps coding errors out of end users' hands.

Generics preserve type safety's strengths while making it easier to implement. Generics bring the concept of type parameters to the .NET Framework. You design classes and methods that adapt to execution requirements, waiting to specify types until client code declares and instantiates those classes and methods. This approach relieves client code of the potential overhead and problems associated with runtime casts and boxing operations.

Normally, you use generics with collections and their methods. The VS 2005 .NET Framework class library includes a new namespace, System.Collections.Generic. This namespace provides several generic-based collection classes.

Currently a C# coder designs a class List {?} and creates runtime instances of the same that can contain one or more objects. These objects can be strings, integers, or any custom data structure. List doesn't know the type of data contained in the collection, so you take a real performance hit at run time from having to box objects to types, then unbox back to objects later. The assumption here is that collections usually hold similar types of data.

The need for collections, boxing, and unboxing reduces the scope of code reuse in the application domain prior to generics (boxing converts an instance of a value type to its base "object" type or to a type interface it implements; unboxing reverses this operation). Object-oriented programming sounds great in concept, but the devil is in the details, and without generics, .NET Framework users have experienced problems with code reuse, flexibility, and performance.

Consider a canonical example using the templated max() method:

Generic <typename T>
T max (T a, T b)
{
   return (b < a) ? a : b;
}

In .NET 1.1, you create and maintain lists where all items added to the List are objects by default. You may provide the value as an integer or string or custom-defined object data structure, but .NET treats them all as objects:

//The .NET Framework 1.1 way of creating a list
ArrayList list1 = new ArrayList();
list1.Add(3);
list1.Add(105);
//...
ArrayList list2 = new ArrayList();
list2.Add("It is raining in Redmond.");
list2.Add("It is snowing in the mountains.");
//...

This code shows you instantiating objects of the ArrayList class, called list1 and list2. You're adding integer and string values to each ArrayList object. Everything works well in this case, because both lists contain unique types of values being stored.

Type Safety Needs Generics
However, the next scenario tries to store different types of data values in the same list. The CLR stores the values, but it throws an InvalidCastException if you write code that iterates through the values in the list and performs arithmetic operations—because different types of data values are stored in the list:

ArrayList list = new ArrayList();  
//This code has no issues and is OK.   
list.Add(3);  
//This code is OK, but do you really 
//want it this way?
list.Add(."It is raining in Redmond."); 

The compiler doesn't raise any warnings or red flags, because it isn't designed to worry about such an error. It's the responsibility of the runtime engine to figure this out. Moreover, even the best testing approaches might skip or ignore this piece of code, so it might wait to break until it's been packaged and deployed to your customer:

int t = 0;
//Returns an InvalidCastException.
      foreach(int x in list) 
{
   t += x;
}

C# 2.0 would raise such an error during compile time, as would VB.NET 2.0 in its own way.

Generics let you reuse the same algorithm by designing it to work on different data or custom types. This sort of code reuse promotes better reliability. You aren't altering the code—and potentially introducing errors—so you don't need to retest it every time you use it with a different type. Designing with generics in VS 2005 radically reduces the number of times you need to perform explicit conversions between data types.

Visual Studio checks the generics you've developed at compile time. You instantiate a generic class with a supplied type parameter (such as MyList<int>). The type parameter can be only of the type specified in the class definition. For example, when you create a List of integers, you're no longer able to push a string onto the stack. Visual Studio's enforcement of this behavior in the code forces your app to be more robust.

Support an argument during compile-time type checking by creating a list called iList of type MyList<int>, then try to store a string as one of the elements. This violates generics rules, so the CLR raises a red flag during compilation (see Figure 1):

//The .NET Framework 2005 way of 
//creating a list
MyList<int> iList = new MyList<int>();
//No boxing, no casting:
iList.AddHead(100);
iList.AddHead(200);
iList.AddHead("String cannot be stored 
   in int List");
//Compile-time error appears

VS 2005 generics reduce code bloat compared to other strongly typed implementations (see Figure 2). Creating typed collections with generics lets you avoid the need to create specific versions of each class, yet the app performs as well as one with class versioning.

For example, you can create one parameterized List class without having to create an IntegerList to store integers, a StringList to store strings, or an EmployeeList to store Employee types.

Put Code Bloat on a Diet
Simplicity produces more readable code. You can encapsulate all behavior associated with a list in one convenient List class. You're still using a list data structure when you create a list of Employee types, albeit one that stores Employee types.

C# without generics lets you say class List {...}. C# with generics lets you say class List<T> {...}, where T is the type parameter. You can use T as if it were a type. You create a List object by writing List<int> or List<Customer>. You construct new types from that List<T>, and it's as if your type arguments are substituted for the type parameter. All the Ts become ints or Customers. You don't have to downcast, and you benefit from strong type checking everywhere.

The CLR compiles down to Intermediate Language (IL) and metadata just like any normal type when you compile List<T> or any other generic type. The IL and metadata contain additional information that knows there's a type parameter, but in principle, a generic type compiles like any other type would compile.

Your application makes its first reference to List<int> at run time. The system looks to see if anyone has asked for List<int> already. If not, the system feeds the IL and metadata for List<T> and the type argument int into the JIT compiler. The compiler also substitutes the type parameter in the process of JITing the IL.

Now define a generic type MyList<T> and see how generics work in the VS 2005 beta. MyList<T> has a nested class called Node and a member field head of the type Node. Use VS 2005's new integrated design tool, code-named Whitehorse, to create a class diagram for you automatically (see Figure 3).

Generic types are available in the System.Collections.Generic namespace:

#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion

Declare a namespace called GenericsTest containing MyList<T>:

namespace GenericsTest
{

Declare a class called Program. I'll use this to demonstrate the capabilities of generic types declared in the class MyList<T>:

   class Program
   {
      static void Main(string[] args)
      {

Module Module1
   Sub Main()

Now implement MyList<T> where <T> is <int> in this example, making this a list of integers:

   MyList<int> iList = new 
      MyList<int>();
   iList.AddHead(100);
   iList.AddHead(200);
   foreach (int iVal in iList)
   {
      Console.WriteLine(iVal);
   }
      Console.Write("Printing 
         Integer Values Done");

Here's the same thing in VB.NET 2.0:

Sub Main()
   Dim iList As MyList(Of Int32)
   iList = New MyList(Of Int32)
   iList.AddHead(100)
   iList.AddHead(200)
   iList.AddHead(300)
   Dim enumerator As IEnumerator
   enumerator=iList.GetEnumerator()
      While enumerator.MoveNext 
         Console.WriteLine( _
            CStr(enumerator.Current))
   End While
End Sub

You can implement MyList<T> in C# 2.0, where <T> is <string>, making this a list of strings:

   MyList<string> sList = new 
      MyList<string>();
   sList.AddHead("String 1");
   sList.AddHead("String 2");
   sList.AddHead("String 3");
   foreach (string sVal in sList)
   {
      Console.WriteLine(sVal);
   }
   Console.Write("Printing String Values Done");
   }
}

It works like this in VB.NET 2.0:

Dim sList As MyList(Of String)
sList = New MyList(Of String)

iList.AddHead("Verizon")
iList.AddHead("AT&T")
iList.AddHead("SBC")

Dim enumerator As IEnumerator
enumerator=sList.GetEnumerator()
   While enumerator.MoveNext 
      Console.WriteLine( _
         CStr(enumerator.Current))
End While

Declare a generic type in C# 2.0 with one type parameter <T>, where <T> can be any built-in or custom type, such as integer, string, float, or Employee:

public class MyList<T> 
   //type parameter T in angle brackets
{
   private Node head;

Here's how it works in VB.NET 2.0:

Public Class MyList(Of itemType)
   Inherits CollectionBase
   Shared head As MyNode

Nest Types Generically
The generic class <T> contains Node, a nested type that operates on data of type T that's a parameter of the generic type:

// Nested type is also generic on T.
   private class Node          
   {
   private Node next;
//T as private member data type:
   private T data;          
//T used in non-generic constructor:
   public Node(T t)         
   {
      next = null;
      data = t;
   }

It works like this in VB.NET 2.0:

'The nested type is also 
'generic on T.
      Private Class MyNode
      Dim lNext As MyNode
      'T as private member data type:
      Dim data As itemType
      Public Sub New()
         lNext = Nothing
      End Sub
   'T used in non-generic constructor:
      Public Sub New(ByVal t As _
         itemType)
         lNext = Nothing
         data = t
      End Sub

This property returns the next node in the list in C# 2.0:

public Node Next
{
   get { return next; }
   set { next = value; }
}

It returns the next node like this in VB.NET 2.0:

Public Property NextNode() As MyNode
   Get
      Return lNext
End Get
Set(ByVal Value As MyNode)
      lNext = Value
   End Set
End Property

This property returns the data from the node in the list in C# 2.0:

//T as return type of property:
   public T Data            
   {
      get { return data; }
      set { data = value; }
   }
}
public MyList()
{
   head = null;
}

It looks like this in VB.NET 2.0:

'T as return type of property:
   Public Property NodeData() _
      As itemType
      Get
         Return data
      End Get
      
      Set(ByVal Value As itemType)
         data = Value
      End Set
   End Property
End Class
Public Sub MyList()
   head = Nothing
End Sub

Use the AddHead method to add the data of type T to the list of type MyList<T>. Maintain the chain of items in the list by maintaining the reference to the next item as data in the current item:

   //T as method parameter type:
      public void AddHead(T t)     
      {
         Node n = new Node(t);
         n.Next = head;
         head = n;
      }
      public IEnumerator<T> 
         GetEnumerator()
      {
         Node current = head; 
         while (current != null)
         {
            yield return current.Data;
            current = current.Next;
         }
      }
   }
}

Here's the VB.NET 2.0 version:

   'T as method parameter type:
   Public Function AddHead(ByVal _
      t As itemType) As Void
      Dim n As New MyNode(t)
      n.NextNode = head
      head = n
   End Function
End Class
End Module

The preceding MyList example uses only one type <T>. However, generic types may employ any number of parameter types. For example, Dictionary stores values alongside the keys. I'll use Dictionary to demonstrate how you can define a generic version of a Dictionary class by declaring two parameters. Commas within the angle brackets of the class definition separate the parameters:

public class Dictionary<KeyType, 
   ValType>
{
   public void Add(KeyType key, 
      ValType val)
   {
      ...
   }
   public ValType this[KeyType key]
   {
      ...
   }
}

You need to supply multiple parameters within the angle brackets of the instantiation statement when using the Dictionary class, again separating by commas and supplying the right types to the parameters of the Add function and indexer:

Dictionary<string, string> dict = new 
   Dictionary<string, string>;
dict.Add("SessionID", new 
   SessionInfo.SessionID());
dict.Add("ApplicationType", 
   "New Install");
string sessionId = 
   dict.Get["SessionID"];

The data structures available in the System.Collections namespace are all object-oriented. This means you're inheriting the classic object-oriented problems of poor performance, lack of code reusability, compromised reliability, and lack of type safety.

Generics introduces a new set of generic collections in VS 2005's System.Collections.Generics namespace, including new generic Stack<T> and generic Queue<T> classes. The Dictionary<Key,Val> data structure in the new namespace is equivalent to the non-generic object-oriented data structure HashTable. You can also find a generic SortedDictionary<K,T> class somewhat like the non-generic SortedList namespace. And the class List<T> compares to the non-generic ArrayList. All the new System.Collections.Generics types map to System.Collections (see Table 1).

On the other hand, VS 2005 doesn't support the use of generics to define Web services—specifically, you can't employ Web methods that use generic type parameters. Currently, none of the Web services standards support generic services.

Microsoft promises to provide support for creation and consumption of generic types in all built-in VS 2005 languages: Visual Basic .NET, Visual C#, and Visual J#. Microsoft is working closely with its partners to have other .NET-based languages create and consume generics, and you can anticipate seeing other .NET languages support generics in the fullness of time.

Generics Resemble Templates
Generics are new to VS, but not to other object-oriented languages. For example, C++ developers use templates similarly. VS 2005 generics are like classes, except they have a type parameter. C++ templates are like macros, except they look like classes. VS 2005 generics differ from C++ templates primarily in the areas of when instantiation and type checking occur.

C# instantiates at run time, and C++ instantiates at compile time (sometimes at link time). This means C++ instantiation happens before you run the program. Also, C# does strong type checking when you compile the generic type. The only member methods available on type T values for a type parameter (such as List<T>) are those of type Object. That's because those are the only methods you can guarantee will exist, generally speaking. So C# generics guarantee that any operation you do on a type parameter will succeed.

C++ is the opposite. It lets you do anything you want to do on a variable of a type parameter type. But it isn't guaranteed to work when you instantiate it, and C++ tends to give you some cryptic error messages. For example, say you have a type parameter T and variables x and y of type T, and you say x + y. Well, you'd better have a + operator overloaded for the + operator of two Ts. Otherwise, you get a cryptic error message. In a sense, C++ templates are actually untyped, or loosely typed, while C# generics are strongly typed.

VS 2005's CLR generics have appealing applications in library development that you can reuse throughout an organization. You can choose to apply generics in specific areas, such as collections, or you can choose to architect complete applications using generics. Either way, generics make your code faster, more reusable, reliable, flexible, and type-safe.

comments powered by Disqus

Featured

  • Microsoft Eases Integration with Semantic Kernel AI SDK

    The basic idea is to provide unified API abstractions, especially for idiomatic C# code, to help platform developers and others work with any provider with standard implementations for caching, telemetry, tool calling and other common tasks.

  • Final .NET 9 Preview Ships with Go-Live License

    Visual Studio developers can now download the SDK for .NET 9 Release Candidate 2 with a go-live license, meaning devs get Microsoft support for production applications even before the framework reaches general availability next month.

  • Upcycle Your Old Laptops into a Kubernetes Cluster

    Learn about Windows-to-Linux conversions and how to break and fix cloud containers -- all while helping to save the world from e-waste with some "sheer geeky fun."

  • Visual Studio's Future: Live Help to 'AI-ify Your App'

    Visual Studio guru Mads Kristensen provided a peek into the IDE's AI future, explaining how while in live-coding it will identify opportunities for your own app to use AI to your advantage.

Subscribe on YouTube