SoFunction
Updated on 2025-03-01

Detailed explanation of generics in C#

Generics are not only part of C#, but also closely integrate with IL code in the assembly. With generics, classes and methods that are independent of the included types can be created. This way, you can do without having to write many methods or classes with the same functions for different types, just create one method or class.
Another option to reduce code is to use the Object class, because the Object class is not safe.
Generic classes use generic types and can replace generic types with specific types as needed. This ensures type safety: if a type does not support generic classes, the compiler will have an error.
Generics are not limited to classes, interfaces and methods can also use generics.
Generics are not a completely new structure. In C++, templates are similar to generics. Generics are not only a structure of C#, but are also defined by CLR. Therefore, even if the generic class is defined in C#, the generic type can be instantiated with a specific type in VB.

1. Generic features

1. Performance

Use non-generic collection classes for value types. When converting value types to reference types and converting reference types to value types, boxing and unboxing operations are required. Although it is easy to operate, the performance loss is relatively large:

var list = new ArrayList();
(4);//Boxing operation, the Add method parameter of ArrayList is Objectforeach(int i in list)
{
(i);//Unbox}

and non-generic and generic collection classes. The List<T> class in the list<T> does not use objects, but defines the type when used.

var list = new List<int>();
(4);
foreach(int i in list)
{
(i);
}

2. Type safety

Use the ArrayList class to add any type to the collection.

var list = new ArrayList();
(4);
(“66”);

An exception will appear during traversal, because not all elements can be forced to be replaced with INT type:

foreach(int i in list)
{
(i);
}

Errors should be detected as early as possible. Using the generic class List<T>, the generic type T defines that allows the use of specific types. With the definition of List<int>, you can only add integer types to the collection.

var list = new ArrayList();
(4);
(66);

3. Reuse of binary code

Generics allow better reuse of binary code. Generic classes can be defined once and can be instantiated with many different types. For example, the List<T> class can be instantiated with int, string, and custom classes.
Generic classes can be defined in one language and used in any other .NET language.

4. Code extension

Because generic classes are defined in assembly, instantiating generic classes with specific types does not copy these classes in IL code. However, when the JIT compiler compiles generic classes into local code, a new class is created for each value type. The reference type shares all the same implementation code for a local class. This is because the reference type only needs 4 bytes of memory address in the instantiated generic class (32 is the system) to reference a reference type. Value types are contained in the memory of the instantiated generic class. At the same time, because each value type has different memory requirements, a new class must be instantiated for each value type.

5. Naming Contract

Using generics in programs will help you to distinguish between generic and non-generic types.
Naming rules for generic types:

  • *The name of a generic type is prefixed with the letter T.
  • *If there are no special requirements, generic types are allowed to be replaced by any class, and if only one generic type can be used, T can be used as the name of the generic type.
    Public class Person{}
  • *If a generic type has specific requirements (such as it must implement an interface or derived from a base class), or if two or more generic types are used, a descriptive name should be given to the generic type:
    public class SortedList{}

2. Type of use

1. First create a non-generic simplified linked list class.

public class LinkedListNode
    {
        public LinkedListNode(object value)
        {
             = value;
        }

        public object Value { get; private set; }//value is sealed to the outside, can only be read, set the value through the constructor        public LinkedListNode Next { get; internal set; }//Record the next node of the current node        public LinkedListNode Prev { get; internal set; }//Record the previous node of the current node    }

public class LinkedList : IEnumerable
    {
        public LinkedListNode First { get; private set; }//Record the first node of the linked list, only externally can read, set the value through internal methods        public LinkedListNode Last { get; private set; }

        public LinkedListNode AddLast(object node)
        {
            //Instantiate a LinkedListNode            var newNode = new LinkedListNode(node);

            //Distinguish whether the linked list contains elements. If not, set newNode as the first element of the linked list            if (First == null)
            {
                First = newNode;
                Last = newNode;
            }
            else
            {
                LinkedListNode previous = Last;//Get the last element of the linked list                 = newNode;// Assign newNode to the next element of the last element                Last = newNode;//Reset the last element of the linked list                 = previous;//newNode becomes the last element of the linked list, set the original last element to the previous element of newNode
                //Note the above order            }
            return newNode;
        }

        //Implement the GetEnumerator() method of the IEnumerable interface, so that external code can traverse the linked table with foreach statement        public IEnumerator GetEnumerator()
        {
            LinkedListNode current = First;
            while (current != null)
            {
                yield return ;//yield creates a state machine of enumerator                current = ;
            }
        }
    }

Client code:

var list1 = new LinkedList();
(2);
(4);
("22");

foreach (object i in list1)
{
(());
}

Packing and unboxing operation is required.

2. Write a generic version below

public class LinkedListNode&lt;T&gt;
    {
        public LinkedListNode(T value)
        {
             = value;
        }

        public T Value { get; private set; }//value is sealed to the outside, can only be read, set the value through the constructor        public LinkedListNode&lt;T&gt; Next { get; internal set; }//Record the next node of the current node        public LinkedListNode&lt;T&gt; Prev { get; internal set; }//Record the previous node of the current node    }

//This class implements the IEnumerable<T> interface, and IEnumerable<T> inherits from the IEnumerable interface, so it is necessary to implement the GetEnumerator() method and IEnumerator<T> GetEnumerator() method.public class LinkedList&lt;T&gt; : IEnumerable&lt;T&gt;
    {
        public LinkedListNode&lt;T&gt; First { get; private set; }//Record the first node of the linked list, only externally can read, set the value through internal methods        public LinkedListNode&lt;T&gt; Last { get; private set; }

        public LinkedListNode&lt;T&gt; AddLast(T node)
        {
            //Instantiate a LinkedListNode            var newNode = new LinkedListNode&lt;T&gt;(node);

            //Distinguish whether the linked list contains elements. If not, set newNode as the first element of the linked list            if (First == null)
            {
                First = newNode;
                Last = newNode;
            }
            else
            {
                LinkedListNode&lt;T&gt; previous = Last;//Get the last element of the linked list                 = newNode;// Assign newNode to the next element of the last element                Last = newNode;//Reset the last element of the linked list                 = previous;//newNode becomes the last element of the linked list, set the original last element to the previous element of newNode
                //Note the above order            }
            return newNode;
        }

        //IEnumerable<T> interface inherits IEnumerable        //Implement the GetEnumerator() method of the IEnumerable<T> interface, so that external code can traverse the linked table with foreach statement        public IEnumerator&lt;T&gt; GetEnumerator()
        {
            LinkedListNode&lt;T&gt; current = First;
            while (current != null)
            {
                yield return ;//yield creates a state machine of enumerator                current = ;
            }
        }

        //Implement the GetEnumerator() method of IEnumerable interface        IEnumerator ()
        {
            return GetEnumerator();
        }
    }

Client code:

var list2 = new LinkedList<int>();
(2);
(4);
(44);

foreach (int i in list2)
{
(i);
}

3. Functions of generic classes

Sometimes some C# keywords are needed when creating generic classes.
Here is an example to introduce these keywords:

public class DocumentManager<TDocument>
      where TDocument : IDocument
  {
    private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();

    public void AddDocument(TDocument doc)
    {
      lock (this)
      {
        (doc);
      }
    }

    public bool IsDocumentAvailable
    {
      get { return  > 0; }
    }

    public void DisplayAllDocuments()
    {
      foreach (TDocument doc in documentQueue)
      {
        ();
      }
    }


    public TDocument GetDocument()
    {
      TDocument doc = default(TDocument);
      lock (this)
      {
        doc = ();
      }
      return doc;
    }

  }

1. Default value

In the GetDocument() method, TDocument needs to be specified as null. However, null cannot be assigned to generic types. Because generic types can also be instantiated as value types, null can only be used for reference types. To solve this problem, you can use the default keyword. By default, null is assigned to the reference type and 0 is assigned to the value type.

2. Constraints

If a generic class needs to call a method in a generic type, a constraint must be added.

public interface IDocument
  {
    string Title { get; set; }
    string Content { get; set; }
  }

  public class Document : IDocument
  {
    public Document()
    {
    }

    public Document(string title, string content)
    {
       = title;
       = content;
    }

    public string Title { get; set; }
    public string Content { get; set; }
  }

When traversing the display in the method, you need to use the property Title of the Document class, which requires constraining the generic type TDocument to include this property. Here, the IDocument interface must be implemented using the generic type TDocument. The where clause specifies this constraint.

Constraint types supported by generics:

constraint illustrate
where T:struct For structural constraints, type T must be a value type
where T:class Class constraint specifies that type T must be a reference type
where T:IFoo Specifying type T must implement interface IFoo
where T:Foo The specified type T must be derived from Foo
where T:new() Constructor constraints, specifying type T must have a default constructor
where T1:T2 This constraint specifies that type T1 is derived from generic type T2. This constraint is also called a naked type constraint.

Multiple constraints can also be merged using generic types. where T:IFoo,new(),MyClass

3. Inheritance

Generic types can implement generic interfaces or derive from a class. Generic classes can be derived from generic base classes. The requirement is that the generic type of the interface must be repeated, or the type of the base class must be specified.
The derived class can be a generic class or a non-generic class.

4. Static members

There is a difference between static members of generic classes and static members of non-generic classes. Static members of generic classes can only be shared in one instance of the class:

public class StaticDemo<T>
{
public static int x;
}

Client code:

StaticDemo&lt;string&gt;.x = 4;
StaticDemo&lt;int&gt;.x = 5;
//There are two sets of static fields。

4. Generic interface

Use generics to define interfaces, and methods defined in interfaces can take generic parameters.

V. Generic Structure

Generic structures are similar to generic classes, but they do not have inheritance features.

6. Generic methods

In generic methods, generic types are defined by methods. Generic methods can be defined in non-generic classes.

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

int i=4;
int j = 5;
Swap<int>(ref i,ref j);

Because the C# compiler will get the parameter type by calling the Swap() method, there is no need to assign the generic type to the method call:

int i=4;
int j = 5;
Swap(ref i,ref j);

1. Generic methods with constraints

public static class Algorithm
{

public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
where TAccount : IAccount
{
decimal sum = 0;

foreach (TAccount a in source)
{
sum += ;
}
return sum;
}

}

public interface IAccount
{
decimal Balance { get; }
string Name { get; }
}


public class Account : IAccount
{
public string Name { get; private set; }
public decimal Balance { get; private set; }

public Account(string name, Decimal balance)
{
 = name;
 = balance;
}
}

Client code:

var accounts = new List<Account>()
{
new Account("Christian", 1500),
new Account("Stephanie", 2200),
new Account("Angela", 1800),
new Account("Matthias", 2400)
};

decimal amount = <Account>(accounts);

2. Generic methods with delegate

public static class Algorithm
{
public static T2 Accumulate<T1, T2>(IEnumerable<T1> source, Func<T1, T2, T2> action)
{
T2 sum = default(T2);
foreach (T1 item in source)
{
sum = action(item, sum);
}
return sum;
}

}

decimal amount = <Account, decimal>(accounts, (item, sum) => sum += );

3. Generic method specifications

Generic methods can be overloaded to define specifications for specific types. This applies to methods with parameters.

public class MethodOverloads
{
public void Foo<T>(T obj)
{
("Foo<T>(T obj), obj type: {0}", ().Name);
}

public void Foo(int x)
{
("Foo(int x)");
}

public void Bar<T>(T obj)
{
Foo(obj);
}
}

If an int is passed, choose a method with int parameter. For other parameter types, the compiler selects a generic method.

var test = new MethodOverloads();
(33);
("abc");

Output:

Foo(int x)
Foo<T>(T obj), obj type: String

The method called is defined during compilation, not during runtime.

(44);

Output:

Foo<T>(T obj), obj type: Int32

The Bar() method chooses the generic Foo() method instead of the int parameter overloading method. Because the compiler selects the Foo() method called by the Bar() method during compilation. Since the Bar() method defines a generic parameter, and the generic Foo() method matches this type, the Foo() method is called.

This is all about this article about C# generics. I hope it will be helpful to everyone's learning and I hope everyone will support me more.