Generics

Generics are a powerful feature allowing definition of application elements without setting a type until the application uses them.

This allows for reuse polymorphism, which optimizes code. Developers also avoid repetitive, excessive, error-prone code such as code for boxing and unboxing types or downcasting. It also allows developers to avoid making guesses about types and data use; they simply leave the door open for anything.

In generics use, the server is implemented once as a generic server; and also used and declared with any type. They employ the < and > brackets to enclose a generic type paramenter. Review an example below:

using System;
using System.Collections.Generic;
namespace GenericApp
{
	public class AGenericArray<T>
	{
		private T[] array;
		public AGenericArray(int size)
		{
			array = new T[size + 1];
		}
		public T getItem(int index)
		{
			return array[index];
		}
		public void setItem(int index, T value)
		{
			array[index] = value;
		}
	}
	class Test
	{
		static void Main(string[] args)
		{
			AGenericArray<int> intgArray = new AGenericArray<int>(7);
			for (int x = 0; x < 7; x++)
			{
				intgArray.setItem(x, x*7);
			}

			//retrieve values
			for (int x = 0; x < 7; x++)
			{
			Console.Write(intgArray.getItem(x) + " ");
			}
			Console.WriteLine();
			AGenericArray<char> charArray = new AGenericArray<char>(5);
			for (int y= 0; y < 5; y++)
			{
				charArray.setItem(y, (char)(y+27));
			}

			//retrieve values
			for (int y = 0; y< 5; y++)
			{
				Console.Write(charArray.getItem(y) + " ");
			}
			Console.WriteLine();
			Console.ReadKey();
		}
	}
}

CONSTRAINTS

Generics are compiled independent of type arguments, so they carry the risk of using methods, properties, or members of generic type with incompatible arguments. Applications must instruct the compiler about client-specified types to be obeyed rather than generic. Three types of constraints exist:

  1. Derivation – This indicates the generic type parameter derives from a base type.
  2. Default Constructor – This indicates the generic type parameter reveals a default public constructor, which has no parameters.
  3. Reference/Value – This limits the generic type parameter to value or reference type.

Generics can have multiple constraints. Note that constraints are optional, but are a best practice.

CASTING

C# only allows implicit casting of generic type parameters to Object or constraint-specified types. This prevents problems because incompatibility is revealed at compile-time.

INHERITANCE

Provide type arguments rather than the generic base class parameter when deriving from a generic base class:

public class BaseClass<T>
{...}
public class SubClass : BaseClass<int>
{...}

If using a generic subclass, a subclass generic parameter can be used as the type:

public class SubClass<T> : BaseClass<T>
{...}

In subclass generic type parameters, repeat all constraints used in the base class. Base classes can define virtual methods with signatures employing generic type parameters. In overrides, subclasses must list the corresponding types. Generic subclasses can use their own generic type parameters in an override.

GENERIC MEMBERS

Generic interfaces, abstract classes, and methods can be defined. They all behave like any other generic base type, and the compiler will infer type. Methods can define generic type parameters specific to their execution scope, which allows for the use of the same method with a different type each time. Generic methods can even be used in a class with no generic use. Though methods can do this, properties and indexers cannot. In defining generic type parameters, the method can also define constraints:

using System;
using System.Collections.Generic;
namespace GenericMethod
{
	class MainProg
	{
		static void Swap<T>(ref T lhs, ref T rhs)
		{
			T temp;
			temp = lhs;
			lhs = rhs;
			rhs = temp;
		}
		static void Main(string[] args)
		{
			int w, x;
			char y, z;
			w = 25;
			x = 30;
			y = 'L';
			z = 'G';

			//show values prior to swap
			Console.WriteLine("Initial integer values:");
			Console.WriteLine("w = {0}, x = {1}", w, x);
			Console.WriteLine("Initial char values:");
			Console.WriteLine("y = {0}, z = {1}", y, z);

			//call swap
			Swap<int>(ref w, ref x);
			Swap<char>(ref y, ref z);

			//show values post swap
			Console.WriteLine("Integer values after swap:");
			Console.WriteLine("w = {0}, x = {1}", w, x);
			Console.WriteLine("Char values after swap:");
			Console.WriteLine("y = {0}, z = {1}", y, z);
			Console.ReadKey();
		}
	}
}

Generic delegates exploit the generic type to perform operations like a delegate which operates with two parameters of any type, and returns any type.