Generics in Microsoft .NET
Part I: how to write generalized but still strongly typed code.
Article by Wim Uyttersprot (wim@u2u.be)
U2U - Brussels (www.u2u.net)
Applies to
- Visual Studio .NET Whidbey
- C# 2.0.Alpha
- VB.NET 2.0.Alpha
Summary At PDC 2003, Microsoft presented the next Microsoft OS with code name “Longhorn” and the next version of Visual Studio.NET code name
“Whidbey”. For developers, the most important innovation is the fact that Microsoft .NET not only becomes entirely part of the OS, but even becomes the core API. Win32
API is definitely over. U2U (www.u2u.net) is preparing a series of articles on
the innovations we may expect in the .NET Framework. One set of articles
will describe the innovations in the .NET languages; topics will be how generics
provide improved code reuse, how iterators simplify the implementation of
enumerator patterns, how anonymous methods can ease your work with delegates,
and how partial types provide easier development and code maintenance.
What are generics?
It looks like a paradox, but it isn’t: by means of .NET
generics it becomes possible to write generalized code that is still strongly
typed. The idea behind generics is to code with parameterized types
instead of with concrete types. In this first article you will learn
how to use generic types in order to create constructed types. The
new generic collection types in the .NET framework will be discussed, and you
will learn how to define your own generic collection types.
Generics are parameterized types (parameterized types are
types that contain type parameters; a parameter type is a type that is used as
parameter.) A generic type can be defined as a class, a struct, an interface or
even as delegate but not as an enumeration. The difference between a normal type
and a generic type is that in a generic type some fields, properties and
arguments may be defined by means of a type parameter. For instance in a normal
class all fields must be of well defined types; such as the types String,
Integer, Color, Date… But in a generic class the type of a field might not be specified
but instead be parameterized by means of a type parameter; a type parameter is
a type that is not yet defined but that will be specified later: When we want
to use a generic type, .NET wants us to specify a type parameter for each type
parameter in order to make a constructed type.
n this article we will illustrate those notions through
several code samples, e.g.:
In code snippet 5 we
will illustrate:
· the generic
type
in C# syntax: ObjectList<ItemType>
in VB.NET syntax: ObjectList(Of ItemType)
· the type
parameter: ItemType
In code snippet 6 we will illustrate:
· the type
parameter: String
· the constructed
type
in C# syntax: ObjectList<string>
in VB.NET syntax: ObjectList(Of String)
Generics can also be methods that are using parameterize
types. But this will be discussed in a later article.
Coding patterns
Most of our time, we are solving problems that we have
solved already in a similar manner. Pattern writers typically lookout for such
kind of recurring problems and define and document common solutions.
But if we want to be pattern developers, how do we write
down our patterns in code?
The complicated strongly typed world
Class builders will typically use strong typing. Therefore
they will specify Option Strict On in VB.NET or use only strongly typed
languages as C#. But in the strongly typed world, writing general code is not
that easy as the types of the references must match to the types of the
objects. Typically inheritance and interfaces are used in order to solve the
matching problems.
In this common approach, inheritance is used in the meaning
of generalization: the general solution is coded by means of base classes and
associations. The specialized solution is then coded in derived classes in
which the virtual base methods and properties become overridden.
Generics and strong typing
But .NET generics offer us a new powerful technique that
allows us to parameterize our types and that we can use in a lot of situations
where strong typing is needed.
Generics are used for two main reasons: (1) for compile-time
type safety as parameterized types can be checked (2) and for code reuse as
generics allow us to implement patterns and algorithms that stay independent of
types. Generics have the advantage that they allow us to write less code, that
they increase the efficiency and therefore also the performance of our code.
.NET generics as they are defined in C# and VB.NET 1.2.Alpha
can be compared to the C++ templates but are not identical. Generics simply do
not have the goal to cover all the C++ generics scenarios. Therefore experienced
C++ programmers must be very careful when designing C# or VB.NET generics as
several C++ features are simply not supported in .NET; the reason is that in
C++ templates are compile-time entities, and in .NET they are runtime entities.
Let us study .NET generics by example. In order to learn how
to work with generics, we will use the new generic collection types
List<T> and Dictionary<K,V> in .NET; Then, in order to learn the
syntax needed for defining our own custom generic types, we will implement our
own generic collection.
The (normal) collection types in .NET that are not type safe
The standard .NET collection types as for example the
ArrayList and Hashtable are not type safe. They typically operate on the Object
class as item type and as result there is simply no compiler control available
when we want to collect our specialized objects.
As example we will create a collection object that is filled
with three Color objects. With the non-generic Hashtable collection, we see
that we have to make use of casting a lot. In code snippet 1, this casting is
required on line 06. Let’s be frank: in order to
accentuate the problem of casting, we use an explicit enumerator instead of
using the foreach idiom (with foreach, no casting would be needed): the
enumerator returned by GetEnumerator on line 10 is of the non-generic
IDictionaryEnumerator type, and as result casting is needed on lines 13 and 14.
Working this way is not type safe as using wrong types will
result in runtime exceptions.
C#
01: Hashtable colors = new Hashtable();
02: colors.Add("red",
Color.Red);
03: colors.Add("green",
Color.Green);
04: colors.Add("blue",
Color.Blue);
05:
06: Color color = (Color)colors["red"]; // <<< casting needed
07: Console.WriteLine("{0} in RGB is {1}:{2}:{3}",
08: color,
color.R, color.G, color.B);
09:
10: IDictionaryEnumerator ie = colors.GetEnumerator();
11: while (ie.MoveNext())
12: {
13: color
= (Color)ie.Value; //
<<< casting needed
14: string key = (string)
ie.Key; // <<< casting needed
15: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}",
16: color,
color.R, color.G, color.B);
17: }
C# Code snippet 1: Use of the non-generic Hashtable collection: casting
is required on lines 6, 13 and 14.
VB.NET
01: Dim colors As
Hashtable = New Hashtable
02: colors.Add("red",
Color.Red)
03: colors.Add("green",
Color.Green)
04: colors.Add("blue",
Color.Blue)
05:
06: Dim c As Color = DirectCast(colors("red"), Color) ' <<< casting needed
07: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}", c, c.R, c.G, c.B)
08:
10: Dim ie As
IDictionaryEnumerator = colors.GetEnumerator()
11: While ie.MoveNext()
13: c
= DirectCast(ie.Value, Color) ' <<< casting needed
14: Dim key As String = DirectCast(ie.Key,
String) '
<<< casting needed
15: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}", c, c.R, c.G, c.B)
16: End While
VB.NET Code snippet 1: Use
of the non-generic Hashtable collection: casting is required on lines 6, 13 and
14.
Constructed types created from the generic collection types
The same code can be written by means of generic collections,
with improved type safety as result. We will start with the simple
List<T> and then discuss the Dictionary<K,V>. The generic
collection types of the .NET Framework 1.2.Alpha are defined in the namespace
System.Collections.Generic
A generic type is defined in a similar way as a non-generic
type, but after the type name one or more type parameters are specified.
Constraints can be added to the type parameter by means of the where keyword.
From a generic type we can create a constructed type. For instance
we will define the colors collection as constructed type of the generic class
List<T>. Figure 1 specifies that the <T> parameter must be derived
from the Object type and therefore the intellisense uses here a new keyword,
the “where” keyword.

Figure 1: The
generic List<T> class as defined in the System.Collections.Generic
namespace. Notice that the intellisense specifies that the type parameter
<T> must be a base class of Object.
In order to construct our colors collection as a constructed
type of a generic type, we must specify a type parameter for the type
parameter: this will be the System.Drawing.Color structure, as it is the type
of the collected item.
As result no casting is needed anymore in code snippet 2 as
the indexer on line 06 is of type Color (compare this to code snippet 1 where
casting was needed). Let’s emphasize that the reason why no casting is needed
is the fact that the Color structure was specified on line 01 as the type
argument of the generic List<T>. As a result, all members of the
List<Color> class will declare the Color type instead of the <T>
type parameter; for example in C# Code snippet 2: on line 02 when using the
“int List<T>.Add(T item)”-method, the signature of the method becomes
“int List<Color>.Add(Color item)” as shown in Figure 2.

Figure 2: Intellisense
for generic List type: notice the Color type instead of the type parameter
<T>
Generic Enumerator types
The enumerators of generic collections are also generic:
List<T>.Enumerator is the enumerator type of List<T>. By creating a
constructed collection type, its nested enumerator is also constructed over the
same type argument. For instance in code snippet 2 on line 10 the type returned
by GetEnumerator is List<Color>.Enumerator, and therefore no casting is
needed as the Current property of the enumerator on line 13 is also of type Color.
C#
using System.Collections.Generic;
...
01: List<Color>
colors = new List<Color>();
02: colors.Add(Color.Red);
03: colors.Add(Color.Green);
04: colors.Add(Color.Blue);
05:
06: Color
color = colors[1]; // <<<
no casting needed
07: Console.WriteLine("{0} in RGB is {1}:{2}:{3}",
08: color,
color.R, color.G, color.B);
09:
10: List<Color>.Enumerator ie = colors.GetEnumerator();
11: while (ie.MoveNext())
12: {
13: color
= ie.Current; // <<< no
casting needed
14: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}",
15: color,
color.R, color.G, color.B);
16: }
C# Code snippet 2: Use of the generic
System.Collections.Generic.List<T> collection
VB.NET
Imports System.Collections.Generic
...
01: Dim colors As List(Of Color) = New List(Of Color)
02: colors.Add(Color.Red)
03: colors.Add(Color.Green)
04: colors.Add(Color.Blue)
05:
06: Dim c As Color =
colors(1) ' <<< no casting
needed
07: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}", c, c.R, c.G, c.B)
08:
10: Dim ie As List(Of Color).Enumerator = colors.GetEnumerator()
11: While ie.MoveNext()
12: c
= ie.Current ' <<< no
casting needed
14: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}", c, c.R, c.G, c.B)
15: End While
VB.NET Code snippet 2: Use
of the generic System.Collections.Generic.List(Of T) collection
System.Collections.Generic.Dictionary<K,V> is the new
dictionary or map collection offered by .NET that will be used in most places
where the Hashtable was previously used, in order to improve the type safety of
our code.

Figure 3: The
generic Dictionary class defined in the System.Collections.Generic namespace.
Notice that the intellisense specifies that the parameterized types <K>
and <V> must be both a base class of Object.
In order to create a colors collection, we specify the
System.Drawing.Color structure again as type argument for the <V> type
parameter as <V> represents the type of the collected item.
As argument for <K> we choose String so that it
becomes the type for the index key. This results in a syntax where all members
of the constructed Dictionary<string,Color> class specify the string and
Color types, even in the code sense as figure 4 is showing.

Figure 4: Intellisense
for the generic Dictionary type: notice the string type instead of type
parameter <K> and the Color type instead of type parameter <V>.
The constructed dictionary type allows us to write type safe
code. Look for example to the indexer (an indexer in C# corresponds to the
default property in VB.NET) in code snippet 3 on line 06 that is of type Color.
Further the type returned by GetEnumerator is
Dictionary<string,Color>.Enumerator, and therefore no casting is needed
as the Current.Value property of the enumerator on line 13 returns a Color and
as the Current.Key property on line 14 returns a string.
C#
using System.Collections.Generic;
...
01: Dictionary<string,Color> colors = new Dictionary<string,Color>();
02: colors.Add("red",
Color.Red);
03: colors.Add("green",
Color.Green);
04: colors.Add("blue",
Color.Blue);
05:
06: Color
color = colors["red"]; //
<<< no casting needed
07: Console.WriteLine("{0} in RGB is {1}:{2}:{3}",
08: color,
color.R, color.G, color.B);
09:
10: Dictionary<string,Color>.Enumerator ie =
colors.GetEnumerator();
11: while (ie.MoveNext())
12: {
13: color
= ie.Current.Value; // <<< no
casting needed
14: string key =
ie.Current.Key; // <<< no
casting needed
15: Console.WriteLine("{0} in RGB is
{1}:{2}:{3}",
16: color,
color.R, color.G, color.B);
17: }
C# Code snippet 3: Use of the generic
System.Collections.Generic.Dictionary<K,V> collection
VB.NET
Imports System.Collections.Generic
...
01: Dim colors As
Dictionary(Of String,Color)=New Dictionary(Of String,Color)
02: colors.Add("red",
Color.Red)
03: colors.Add("green",
Color.Green)
04: colors.Add("blue",
Color.Blue)
05:
06: Dim c As
Color = colors("red")
07: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}", c, c.R, c.G, c.B)
08:
09: Dim ie As
Dictionary(Of String,
Color).Enumerator =colors.GetEnumerator()
10: While ie.MoveNext()
11: c
= ie.Current.Value
12: Dim key As String = ie.Current.Key
13: Console.WriteLine("{0}
in RGB is {1}:{2}:{3}", c, c.R, c.G, c.B)
14: End While
VB.NET Code snippet 3: Use
of the generic System.Collections.Generic.Dictionary(Of K, V) collection
A simple example of a user-defined generic type
It is not that difficult to define our own generic type when
we keep in mind that a type can become a parameter. Suppose we have coded two
simple non-generic collections that both contain an ArrayList, and both define
a type safe Add method and indexer. Hereby the Persons collection collects
Person objects and the Orders collection groups Order objects. (We chose to
make the example as simple as possible; it is a good exercise to start with a
more complex collection.).
C#
|
public class Persons
{
private ArrayList list;
public Persons()
{
list = new ArrayList();
}
public Persons(int capacity)
{
list = new ArrayList(capacity);
}
public int Add(Person value)
{
return list.Add(value);
}
public Person this[int index]
{
get
{
return (Person)list[index];
}
set
{
list[index] = value;
}
}
}
|
public class Orders
{
private ArrayList list;
public Orders()
{
list = new ArrayList();
}
public Orders(int capacity)
{
list = new ArrayList(capacity);
}
public int Add(Order value)
{
return list.Add(value);
}
public Order this[int index]
{
get
{
return (Order)list[index];
}
set
{
list[index] = value;
}
}
}
|
C# Code snippet 4: Two
non-generic collections with item types can be parameterized
VB.NET
|
Public Class Persons
Dim list As ArrayList
Public Sub New()
list = New ArrayList
End Sub
Public Sub New(ByVal capacity _
As Integer)
list = New ArrayList(capacity)
End Sub
Public Function Add (ByVal
value _
As Person) As Integer
Return list.Add(value)
End Function
Default Public Property Item( _
ByVal index As Integer)
As Person
Get
Return DirectCast( _
list.Item(index), Person)
End Get
Set(ByVal Value As
Person)
list.Item(index) = Value
End Set
End Property
End
Class
|
Public
Class Orders
Dim list As ArrayList
Public Sub New()
list = New ArrayList
End Sub
Public Sub New(ByVal capacity _
As Integer)
list = New ArrayList(capacity)
End Sub
Public Function Add (ByVal
value _
As Order) As Integer
Return list.Add(value)
End Function
Default Public Property Item( _
ByVal index As Integer) As Order
Get
Return DirectCast( _
list.Item(index), Order)
End Get
Set(ByVal Value As Order)
list.Item(index) = Value
End Set
End Property
End
Class
|
VB.NET Code snippet 4: Two
non-generic collections with item types can be parameterized
In the example of code snippet 4, it is simple to see a type
that can be parameterized. The collection types Persons and Orders are quite
identical as they only differ by the type of the items they collect: the Person
class and the Order class.
Syntax for defining generic types
The simple syntax for defining a generic type consists of
defining the type name and its type parameters. For a class this becomes:
C#
public class NameOfClass <TypeParameter>
{
//...
}
VB.NET
Public Class NameOfClass (Of TypeParameter)
' ...
End Class
Our simple generic type ObjectList<ItemType>
Generics allow us to parameterize such types; we do this for
example by defining the parameter <ItemType> that can represent the
Person and Order classes but also any other type. As shown in code snippet 5 on
line 01, we give our generic collection class the name ObjectList, the
parameterized type name of our generic class becomes ObjectList<ItemType>.
C#
01: public class ObjectList<ItemType>
02: {
03: private ArrayList list;
04:
05: public ObjectList()
06: {
07: list = new ArrayList();
08: }
09:
10: public ObjectList(int capacity)
11: {
12: list = new ArrayList(capacity);
13: }
14:
15: public int Add(ItemType value)
16: {
17: return list.Add(value);
18: }
19:
20: public ItemType this[int index]
21: {
22: get
23: {
24: return (ItemType)list[index];
25: }
26: set
27: {
28: list[index]
= value;
29: }
30: }
31: }
C# Code snippet 5: Our
generic class ObjectList has one type parameter <ItemType> and uses for
simplicity the ArrayList as embedded collection object. It defines a type safe
Add method and indexer.
VB.NET
01: Public Class ObjectList(Of ItemType As Object)
02: Dim list ArrayList
03:
04: Public Sub New()
05: list = New ArrayList
06: End Sub
07:
08: Public Sub New(ByVal capacity As Integer)
09: list = New ArrayList(capacity)
10: End Sub
11:
12: Public Function Add(ByVal value As
ItemType) As Integer
13: Return list.Add(value)
14: End Function
15:
16: Default Public Property Item(ByVal
index As Integer)
As ItemType
17: Get
18: Return DirectCast(list.Item(index),
ItemType)
19: End Get
21: Set(ByVal Value As ItemType)
22: list.Item(index)
= Value
23: End Set
24: End Property
25: End Class
VB.NET Code snippet 5: Our
generic class ObjectList has one type parameter “Of ItemType” and uses for
simplicity the ArrayList as embedded collection object. It defines a type safe
Add method and indexer.
The ClassView of Visual Studio.NET is able to list
user-defined generic types as show in Figure 5.

Figure 5: An
example of a user-defined generic type in ClassView.
Our constructed types
We can now create constructed types from our user-defined
generic type by specifying an argument for the type parameter <ItemType>.
On line 01 in code snippet 6, we use as argument the String class; on the next
lines we use the constructed collection class as a collection of strongly typed
String.
C#
01: ObjectList<string> strings = new
ObjectList<string>(10);
02: strings.Add("Red");
03: strings.Add("Green");
04: strings.Add("Blue");
05: string s = strings[1];
06: Console.WriteLine("String
{0} with length {1}", s, s.Length);
C# Code snippet 6:
Coding with our user-defined generic types.
VB.NET
01: Dim
strings As ObjectList(Of String) = New ObjectList(Of
String)(10)
02: strings.Add("Red")
03: strings.Add("Green")
04: strings.Add("Blue")
05: Dim s As String = strings(1)
06: Console.WriteLine("String
0 with length 1", s, s.Length)
VB.NET Code snippet 6:
Coding with our user-defined generic types.
Another technique to create new types from generic types is
to take advantage of inheritance. For example in code snippet 7 the base class
of Strings is the constructed class ObjectList<string>. The main
advantage of this powerful technique is that we can use the derived classes in
a familiar way as shown in code snippet 8.
C#
01: public class Strings: ObjectList<stri&ng>
02: {
03: }
C# Code snippet 7: A powerful
technique: using our constructed type as base class.
01: Strings
strings = new Strings();
02: strings.Add("Red");
03: strings.Add("Green");
04: strings.Add("Blue");
05: string s = strings[1];
06: Console.WriteLine("String
{0} with length {1}", s, s.Length);
C# Code snippet 8:
Working with the Strings class of code snippet 7; its base class is a
constructed type.
VB.NET
01: Public Class Strings
02: Inherits ObjectList(Of
String)
03: End Class
VB.NET Code snippet 7: A
powerful technique: using our constructed type as base class.&
01: Dim strings As
Strings = New Strings
02: strings.Add("Red")
03: strings.Add("Green")
04: strings.Add("Blue")
05: Dim s As String = strings(1)
06: Console.WriteLine("String
0 with length 1", s, s.Length)
VB.NET Code snippet 8:
Working with the Strings class of of code snippet 7Error! Reference source not found.; its base class is a
constructed type.
Generics are part of the IL language. When we use ILDASM to
disassemble our assembly, we notice that ILDASM uses a special icon for generic
types. For our example, Figure 6 shows for ObjectList the outline
of the class symbol.

Figure 6: An
example of a user-defined generic type in ILDASM.
The disassembled code of code snippet 6 is shown in snippet
9, where you can see that the type ObjectList<string> is explicitly
revealed.
.method private hidebysig static void
TestStrings() cil managed
{
// Code size 75 (0x4b)
.maxstack 4
.locals init (class U2U.ObjectList<string>
V_0,
string V_1)
IL_0000: ldc.i4.s 10
IL_0002: newobj instance void class
U2U.ObjectList<string>::.ctor(int32)
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldstr "Red"
IL_000e: callvirt
instance int32 class U2U.ObjectList<string>::Add(!0)
IL_0013: pop
IL_0014: ldloc.0
IL_0015: ldstr "Green"
IL_001a: callvirt
instance int32 class U2U.ObjectList<string>::Add(!0)
IL_001f: pop
IL_0020: ldloc.0
IL_0021: ldstr
"Blue"
IL_0026: callvirt instance int32 class U2U.ObjectList<string>::Add(!0)
IL_002b: pop
IL_002c: ldloc.0
IL_002d: ldc.i4.1
IL_002e: callvirt instance !0 class
U2U.ObjectList<string>::get_Item(int32)
IL_0033: stloc.1
IL_0034: ldstr "String {0} with length
{1}"
IL_0039: ldloc.1
IL_003a: ldloc.1
IL_003b: callvirt
instance int32 [mscorlib]System.String::get_Length()
IL_0040: box [mscorlib]System.Int32
IL_0045: call void
[mscorlib]System.Console::WriteLine(string,
object,
object)
IL_004a: ret
} // end of method Client::TestStrings
Code snippet 9: The
disassembled code of C# Code snippet 6
9
Generic collections for value types
In the simple example of code snippet 5 where we defined our
custom generic type ObjectList<ObjectType>, we used an embedded ArrayList
collection. This works well if we only use reference types for the type
parameter <ObjectType>, as shown with Person and Order.
But if we use a value type as type parameter, the embedded
ArrayList is not optimal as boxing occurs each time when we add the value
object to the ArrayList and as unboxing occurs when we retrieve the object from
the ArrayList.
The new generic collection types in .NET have the advantage
against their non-generic counterparts that they also work optimally with value
types, i.e. without the need of using boxing/unboxing. Therefore it is better
to use the generic type List<T> instead of the non-generic type
ArrayList. So to end this article, let’s adapt our example of the
ObjectList<ItemType> collection type and define the list reference to be
of constructed type List<ItemType>. See line 3 of code snippet 10.
C#
01: public class ObjectList<ItemType>
02: {
03: private List<ItemType> list;
04:
05: public
ObjectList()
06: {
07: list = new
List<ItemType>();
08: }
09:
10: public
ObjectList(int capacity)
11: {
12: list = new
List<ItemType>(capacity);
13: }
14:
15: public
int Add(ItemType value)
16: {
17: return
list.Add(value);
18: }
19:
20: public
ItemType this[int
index]
21: {
22: get
23: {
24: return (ItemType)list[index];
25: }
26: set
27: {
28: list[index] = value;
29: }
30: }
31: }
C# Code snippet 10: A better version of ObjectList, as on
line 3 the embedded collection is defined of generic type List<T>
VB.NET
01: Public Class
ObjectList(Of ItemType As
Object)
02:
03: Dim list As
List(Of ItemType)
04:
05: Public Sub
New()
06: list = New List(Of ItemType)
07: End Sub
08:
09: Public Sub
New(ByVal
capacity As Integer)
10: list = New List(Of ItemType)(capacity)
11: End Sub
12:
13: Public Function
Add(ByVal value As
ItemType) As Integer
14: Return list.Add(value)
15: End Function
16:
17: Default Public
Property Item(ByVal
index As Integer)
As ItemType
18: Get
19: Return DirectCast(list.Item(index),
ItemType)
21: End Get
22: Set(ByVal
Value As ItemType)
23: list.Item(index)
= Value
24: End Set
25: End Property
26: End Class
VB.NET Code snippet 10: A
better version of ObjectList, as on line 3 the embedded collection is defined
of generic type List<T>
Sample code
The sample code of the examples is available on the U2U
website on http://www.u2u.net/downloads/generics1.zip
About the author
Wim Uyttersprot (wim@u2u.be)
is founder and director of U2U. He is a Master in Science in Physics, is
certified as MCSD and MCT in VB.NET, C# and C++.NET. U2U is as Microsoft Certified Technical Education Center specialized in .NET development. More info on
www.u2u.net