generics in c#

Generic in c# -: Generics refer to the use of type parameters, which provide a way to design code templates that can operate with different data types. Specifically, it is possible to create generic methods, classes, interfaces, delegates, and events.

Generic Methods-:  In the following example, there is a method that swaps two integer arguments. as like

static void Swap(ref int x, ref int y)
{
  int temp = x;
  x = y;
  b = temp;
}

To make this into a generic method that can work with any data type, x type parameter first needs to be added after the method’s name, enclosed between angle brackets. The naming convention for type parameters is that they should start with a capital T, and then have each word that describes the parameter initially capitalized. In cases such as this however, where a descriptive name would not add much value, it is common to simply name the parameter with a capital T

static void Swap<T>(ref int x, ref int y)
{
  int temp = x;
  x = y;
  y = temp;
}

The type parameter can now be used as any other type inside the method, and so the second thing that needs to be done to complete the generic method is to replace the data type that will be made generic with the type parameter.

static void Swap<T>(ref T x, ref T y)
{
  T temp = x;
  x = y;
  y = temp;
}

Calling Generic Methods-: The generic method is now finished. To call it, the desired type argument needs to be specified in angle brackets before the method arguments.

int x = 0, y = 1;
Swap<int>(ref x, ref y);

In this example  the generic method may also be called as if it were a regular method, without specifying the type argument.  because the compiler can automatically determine the type since the generic method’s parameters use the type parameter. However, if this was not the case, or to use another type argument than the one the compiler would select, the type argument would then need to be explicitly specified.

Swap(ref x, ref y);

Whenever a generic is called for the first time during runtime, a specialized version of the generic will be instantiated that has every occurrence of the type parameter substituted with the specified type argument. It is this generated method that will be called and not the generic method itself. Calling the generic method again with the same type argument will reuse this instantiated method.

Swap<int>(ref x, ref y); // create & call Swap<int>
Swap<int>(ref x, ref y); // call Swap<int>

When the generic method is called with a new type, another specialized method will be instantiated.

long c = 0, d = 1;
Swap<long>(ref c, ref d); // create & call Swap<long>

Generic Type Parameters-: A generic can be defined to accept more than one type parameter just by adding more of them between the angle brackets. Generic methods can also be overloaded based on the number of type parameters that they define.

static void Dummy<T, U>() {}
static void Dummy<T>() {}

Default Value-: When using generics, one issue that may arise is how to assign a default value to a type parameter since this value depends on the type. The solution is to use the default keyword followed by the type parameter enclosed in parentheses. This expression will return the default value no matter which type parameter is used.

static void Reset<T>(ref T a)
{
  a = default(T);
}

Generic Classes-: Generic classes allow class members to use type parameters. They are defined in the same way as generic methods, by adding a type parameter after the class name.

class Point<T>
{
  public T x, y;
}

For this example  an object from the generic class, the standard notation is used, but with the type argument specified after both class names. Note that in contrast to generic methods , a generic class must always be instantiated with the type argument explicitly specified.

Point<short> p = new Point<short>();

Generic Class Inheritance-: Inheritance works slightly differently with generic classes. A generic class can inherit from a non-generic class, also called a concrete class. Second, it can inherit from another generic class that has its type argument specified, a so-called closed constructed base class. Finally, it can inherit from an open constructed base class, which is a generic class that has its type argument left unspecified.

class BaseConcrete  {}
class BaseGeneric<T>{}
class Gen1<T> : BaseConcrete    {} // concrete
class Gen2<T> : BaseGeneric<int>{} // closed constructed
class Gen3<T> : BaseGeneric<T>  {} // open constructed

A generic class that inherits from an open constructed base class must define all of the base class’s type arguments, even if the derived generic class does not need them. This is because only the child class’s type arguments can be sent along when the child class is instantiated.

class BaseMultiple<T, U, V> {}
class Gen4<T, U> : BaseMultiple<T, U, int> {}

This also means that a non-generic class can only inherit from a closed constructed base class, and not from an open one, because a non-generic class cannot specify any type arguments when it is instantiated.

class Con1 : BaseGeneric<int> {} // ok
class Con2 : BaseGeneric<T> {}   // error

Generic Interfaces-: Interfaces that are declared with type parameters become generic interfaces. Generic interfaces have the same two purposes as regular interfaces. They are either created to expose members of a class that will be used by other classes, or to force a class to implement a specific functionality. When a generic interface is implemented, the type argument must be specified. The generic interface can be implemented by both generic and non-generic classes.

// Generic functionality interface
interface IGenericCollection<T>
{
  void store(T t);
}
// Non-generic class implementing generic interface
class Box : IGenericCollection<int>
{
  public int myBox;
  public void store(int i) { myBox = i; }
}
// Generic class implementing generic interface
class GenericBox<T> : IGenericCollection<T>
{
  public T myBox;
  public void store(T t) { myBox = t; }
}

Generic Delegates-: A delegate can be defined with type parameters. As an example, the following generic delegate uses its type parameter to specify the referable method’s parameter. From this delegate type, a delegate object can be created that can refer to any void method that takes a single argument, regardless of its type.

class MyClass
{
  public delegate void MyDelegate<T>(T arg);
  public void Print(string s)
  {
    System.Console.Write(s);
  }
  static void Main()
  {
    MyDelegate<string> d = Print;
  } 
}

Generic Events-: Generic delegates can be used to define generic events. For example, instead of using the typical design pattern where the sender of the event is of the Object type, a type parameter can allow the sender’s actual type to be specified. This will make the argument strongly-typed, which allows the compiler to enforce that the correct type is used for that argument.

delegate void MyDelegate<T, U>(T sender, U eventArgs);

event MyDelegate<MyClass, System.EventArgs> myEvent;

Generics and Object:- In general, using the Object type as a universal container should be avoided. The reason why Object containers, such as the Array List, exist in the .NET class library is because generics were not introduced until C# 2.0. When compared with the Object type, generics not only ensure type safety at compile-time, but they also remove the performance overhead associated with boxing and unboxing value types into an Object container.

// Object container class
class MyBox { public object o; }
// Generic container class
class MyBox { public T o; }
class MyClass
{
  static void Main()
  {
    // .NET object container
    System.Collections.ArrayList a;
    // .NET generic container (preferred)
    System.Collections.Generic.List b;
  } 
}

Constraints-: When defining a generic class or method, compile-time enforced restrictions can be applied on the kinds of type arguments that may be used when the class or method is instantiated. These restrictions are called constraints and are specified using the where keyword. All in all there are six kinds of constraints. First, the type parameter can be restricted to value types by using the struct keyword.

class C<T> where T : struct {} // value type

Second, the parameter can be constrained to reference types by using the class keyword.

class D<T> where T : class {} // reference type

Third, the constraint can be a class name. This will restrict the type to either that class or one of its derived classes.

class B {}
class E<T> where T : B {} // be/derive from base class

Fourth, the type can be constrained to either be or derive from another type parameter.

class F<T, U> where T : U {} // be/derive from U

The fifth constraint is to specify an interface. This will restrict the type parameter to only those types that implement the specified interface, or that is of the interface type itself.

interface I {}
class G<T> where T : I {} // be/implement interface

Finally, the type argument can be constrained to only those types that have a public parameter-less constructor.

class H<T> where T : new() {} // no parameter constructor

Multiple Constraints-:Multiple constraints can be applied to a type parameter by specifying them in a comma-separated list. Furthermore, to constrain more than one type parameter, additional where clauses can be added. Note that if either the class or the struct constraint is used, it must appear first in the list. Moreover, if the parameter-less constructor constraint is used, it must be the last one in the list.

class J<T, U>
  where T : class, I
  where U : I, new() {}

Features of Generics-: Generics is a technique that enriches our programs in the following ways −

  • It helps you to maximise code reuse, type safety, and performance.
  • we can create generic collection classes.
  • The .NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace.
  • we can use these generic collection classes instead of the collection classes in the System.Collections namespace.
  • we can create own generic interfaces, classes, methods, events, and delegates.
  • We can  create generic classes constrained to enable access to methods on particular data types.
  • We get information on the types used in a generic data type at run-time by means of reflection

 

A programme of generic in c#-:

using System;
using System.Collections.Generic;

namespace GenericMethodAppl {
   class Program {
      static void Swap(ref T lhs, ref T rhs) {
         T temp;
         temp = lhs;
         lhs = rhs;
         rhs = temp;
      }
      static void Main(string[] args) {
         int x, y;
         char u, v;
         x = 10;
         y = 20;
         u = 'S';
         v = 'R';
         
         //display values before swap:
         Console.WriteLine("Int values before calling swap:");
         Console.WriteLine("x = {0}, y = {1}", x, y);
         Console.WriteLine("Char values before calling swap:");
         Console.WriteLine("u = {0}, v = {1}", u, v);
         
         //call swap
         Swap(ref x, ref y);
         Swap(ref u, ref v);
         
         //display values after swap:
         Console.WriteLine("Int values after calling swap:");
         Console.WriteLine("x = {0}, y = {1}", x, y);
         Console.WriteLine("Char values after calling swap:");
         Console.WriteLine("u = {0}, v = {1}", u, v);
         
         Console.ReadKey();
      }
   }
}

Output of this programme

Int values before calling swap:
x = 10, y = 20
Char values before calling swap:
u = S, v = R
Int values after calling swap:
x = 20, y = 10
Char values after calling swap:
u = R, v = S

 

 

Leave a Comment