TheDeveloperBlog.com


C# Generic Class

Generic classes have type parameters. Separate classes, each with a different field type in them, can be replaced with a single generic class. The generic class introduces a type parameter. This becomes part of the class definition itself.

Type

Example. One amazing feature in the C# language is the ability to specify and use generic types. These types themselves contain type parameters, which influence the compilation of the code to use any type you specify.

Next: In this example, the letter T denotes a type that is only known based on the calling location.

And: The program can act upon the instance of T like it is a real type, but it is not.

C# program that describes generic class

using System;

class Test<T>
{
    T _value;

    public Test(T t)
    {
	// The field has the same type as the parameter.
	this._value = t;
    }

    public void Write()
    {
	Console.WriteLine(this._value);
    }
}

class Program
{
    static void Main()
    {
	// Use the generic type Test with an int type parameter.
	Test<int> test1 = new Test<int>(5);
	// Call the Write method.
	test1.Write();

	// Use the generic type Test with a string type parameter.
	Test<string> test2 = new Test<string>("cat");
	test2.Write();
    }
}

Output

5
cat

Generic class constraints. The C# language also provides ways for you to add more features to your generic types by reducing the range of types they can be parameterized with. This feature is only occasionally useful.


Constraints. With constrained generic types, we can implement required functionality that depends on certain features. This program uses three classes that demonstrate different ways of constraining type parameters.

The Ruby class requires that its type parameter implement IDisposable. This gives you the ability to call the Dispose method or using statement on the type parameter variable in the class. This is not shown.

C# program that uses generic type constraints

using System;
using System.Data;

/// <summary>
/// Requires type parameter that implements interface IEnumerable.
/// </summary>
class Ruby<T> where T : IDisposable
{
}

/// <summary>
/// Requires type parameter that is a struct.
/// </summary>
class Python<T> where T : struct
{
}

/// <summary>
/// Requires type parameter that is a reference type with a constructor.
/// </summary>
class Perl<V> where V : class, new()
{
}

class Program
{
    static void Main()
    {
	// DataTable implements IDisposable so it can be used with Ruby.
	Ruby<DataTable> ruby = new Ruby<DataTable>();

	// Int is a struct (ValueType) so it can be used with Python.
	Python<int> python = new Python<int>();

	// Program is a class with a parameterless constructor (implicit)
	// ... so it can be used with Perl.
	Perl<Program> perl = new Perl<Program>();
    }
}

Python (struct). The Python generic type demands that its type parameter be a struct, or value type in general. Remember, in the C# language, integers (int) are structs so you could use Python<int> as the constructed type.

Struct

Perl (class, new()). The Perl type requires that its type parameter V be a reference type (a class). It also requires that the class has a public parameterless constructor. Every class has a default public constructor.

Default Constructor

In Main, we demonstrate that the program compiles correctly. The Ruby type was used with the DataTable type parameter, which implements IDisposable. The Python type was used with an int parameter.

DataTable

Finally: The Perl type was used with the Program type, which has an implicit, public parameterless constructor.


Discussion. Generic constrains are not often useful. If you are developing a complex framework (like the .NET Framework itself) they are necessary. But for a normal or even complex program, constraints are of limited application.

Tip: Typically, you do not need to constrain type parameters in your own generic types.

Also: You rarely need to make your own generic types now that the .NET Framework already contains so many useful ones.

Collections

Therefore: It is a better use of your time to learn how to use the ones that were already built.

I think the type constraint system shown here is most useful for learning about the generic type design itself. The C# language is a fascinating study of compiler and language design. It is an advanced language in popular use.

Note: Generic types can be constrained using the "where" syntax in the C# programming language.

And: They are not useful in every program. But they help inform us as to the nature of generic types.

Also: They provide features for generics, particularly those found in the Framework itself.

.NET

Terms. In programming, using the correct terminology is helpful. In the C# specification, you will see lots of terms that apply to generic types. These terms include open types, closed types, bound types and unbound types.

Constructed type: This is another term for a generic type—it refers to a type with a type parameter.

Open type: This is also a term for a generic type. It is a type that has type parameters.

Closed type: As opposed to an open type, a closed type is one that does not have type parameters.

Unbound type: An unbound type is declared by a type declaration, but is not actually a type.

Bound type: A bound type is one of two types—a regular type or a constructed type. It is a type of types.


MultiMap. One collection that is often wanted in C# programs is a Dictionary (Map) that has many values at a single key. This allows fast lookup of Lists. The implementation here is not ideal, but uses the generic class syntax. It compiles.

MultiMap

Summary. Usually you will use generic types that are provided by the .NET Framework. These include the Dictionary and List. But you can provide your own types. This ability is often reserved only for types that are used in many ways.