Hướng dẫn sử dụng C# Generics
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Kiểu Generic Class, Interface

1.1- Class Generics

Ví dụ dưới đây định nghĩa ra một class generics. KeyValue là một class generics nó chứa một cặp khóa và giá trị (key/value).
 
KeyValue.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class KeyValue<K, V>
   {

       private K key;
       private V value;

       public KeyValue(K key, V value)
       {
           this.key = key;
           this.value = value;
       }

       public K GetKey()
       {
           return key;
       }

       public void SetKey(K key)
       {
           this.key = key;
       }

       public V GetValue()
       {
           return value;
       }

       public void SetValue(V value)
       {
           this.value = value;
       }

   }

}
K, V trong class KeyValue<K,V> được gọi là tham số generics nó là một kiểu dữ liệu nào đó. Khi sử dụng class này bạn phải xác định kiểu tham số cụ thể.

Hãy xem ví dụ sử dụng class KeyValue.
 
KeyValueDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class KeyValueDemo
   {

       public static void Main(string[] args)
       {

 
           // Tạo một đối tượng KeyValue
           // int: Số điện thoại  (K = int)
           // string: Tên người dùng.  (V = string).
           KeyValue<int, string> entry = new KeyValue<int, string>(12000111, "Tom");

           // C# hiểu kiểu trả về là int (K = int).
           int phone = entry.GetKey();

           // C# hiểu kiểu trả về là string (V = string).
           string name = entry.GetValue();

           Console.WriteLine("Phone = " + phone + " / name = " + name);

           Console.Read();
       }

   }

}
Chạy ví dụ:

1.2- Thừa kế class Generics

Một class mở rộng từ một class generics, nó có thể chỉ định rõ kiểu cho tham số generics, giữ nguyên các tham số generics hoặc thêm các tham số generics.

Ví dụ 1:

PhoneNameEntry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   // Class này mở rộng từ class KeyValue<K,V>
   // Và chỉ định rõ K,V
   // K = int  (Số điện thoại).
   // V = string   (Tên người dùng).
   public class PhoneNameEntry : KeyValue<int, string>
   {

       public PhoneNameEntry(int key, string value)
           : base(key, value)
       {

       }

   }

}
Ví dụ sử dụng PhoneNameEntry:
PhoneNameEntryDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class PhoneNameEntryDemo
   {

       public static void Main(string[] args)
       {

           PhoneNameEntry entry = new PhoneNameEntry(12000111, "Tom");

           // C# hiểu kiểu trả về là int.
           int phone = entry.GetKey();

           // C# hiểu kiểu trả về là string.
           string name = entry.GetValue();

           Console.WriteLine("Phone = " + phone + " / name = " + name);

           Console.Read();

       }

   }

}

Ví dụ 2:

StringAndValueEntry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   // Class này mở rộng class KeyValue<K,V>
   // Xác định rõ kiểu tham số K là String.
   // Vẫn giữ kiểu tham số generic V.
   public class StringAndValueEntry<V> : KeyValue<string, V>
   {

       public StringAndValueEntry(string key, V value)
           : base(key, value)
       {
       }

   }

}
Ví dụ sử dụng StringAndValueEntry class:
StringAndValueEntryDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class StringAndValueEntryDemo
   {

       public static void main(String[] args)
       {

           // (Mã nhân viên, Tên nhân viên).
           // V = string (Tên nhân viên)
           StringAndValueEntry<String> entry = new StringAndValueEntry<String>("E001", "Tom");

           String empNumber = entry.GetKey();

           String empName = entry.GetValue();

           Console.WriteLine("Emp Number = " + empNumber);
           Console.WriteLine("Emp Name = " + empName);

           Console.Read();

       }
   }

}

Ví dụ 3:

KeyValueInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

  // Class này mở rộng class KeyValue<K,V>
  // Nó có thêm một tham số generics I.
  public class KeyValueInfo<K, V, I> : KeyValue<K, V>
  {

      private I info;

      public KeyValueInfo(K key, V value)
          : base(key, value)
      {
      }

      public KeyValueInfo(K key, V value, I info)
          : base(key, value)
      {
          this.info = info;
      }

      public I GetInfo()
      {
          return info;
      }

      public void SetInfo(I info)
      {
          this.info = info;
      }

  }

}

1.3- Interface Generics

Một Interface có tham số Generics:
GenericInterface.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
  public interface GenericInterface<G>
  {

      G DoSomething();

  }

}
Ví dụ một class thi hành Interface:
GenericInterfaceImpl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   public class GenericInterfaceImpl<G> : GenericInterface<G>
   {

       private G something;

       public G DoSomething()
       {
           return something;
       }

   }

}

1.4- Sử dụng Generic với Exception

Bạn có thể định nghĩa một Exception có các tham số Generics.
MyException.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class MyException<E> : ApplicationException
   {

   }

}
Sử dụng Generic Exception hợp lệ:
UsingGenericExceptionValid01.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class UsingGenericExceptionValid01
   {

       public void SomeMethod()
       {
           try
           {
               // ...

           }
           // Hợp lệ
           catch (MyException<string> e)
           {
               // Làm gì đó ở đây.
           }
           // Hợp lệ
           catch (MyException<int> e)
           {
               // Làm gì đó ở đây.              
           }
           catch (Exception e)
           {
           }
       }

   }

}
Sử dụng Generics Exception hợp lệ:
UsingGenericExceptionValid02.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class UsingGenericExceptionValid02<K>
   {
       public void SomeMethod()
       {
           try
           {
               // ...

           }
           // Hợp lệ
           catch (MyException<string> e)
           {
               // Làm gì đó ở đây.
           }
           // Hợp lệ
           catch (MyException<K> e)
           {
               // Làm gì đó ở đây.              
           }
           catch (Exception e)
           {
           }
       }
   }

}
Sử dụng Generics Exception không hợp lệ:
UsingGenericExceptionInvalid.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class UsingGenericExceptionInvalid
   {
       public void SomeMethod()
       {
           try
           {
               // ...

           }
           // Hợp lệ
           catch (MyException<string> e)
           {
               // Làm gì đó ở đây.
           }
           // Không hơp lệ (Không hiểu tham số K). ***********
           catch (MyException<K> e)
           {
               // Làm gì đó ở đây.              
           }
           catch (Exception e)
           {
           }
       }
   }

}

2- Phương thức generics

Một phương thức trong class hoặc Interface có thể được generic hóa (generify).
MyUtils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class MyUtils
   {
       // <K,V> : Nói rằng phương thức này có 2 kiểu tham số K,V
       // Phương thức trả về kiểu K.
       public static K GetKey<K, V>(KeyValue<K, V> entry)
       {
           K key = entry.GetKey();
           return key;
       }

       // <K,V> : Nói rằng phương thức này có 2 kiểu tham số K,V
       // Phương thức trả về kiểu V.
       public static V GetValue<K, V>(KeyValue<K, V> entry)
       {
           V value = entry.GetValue();
           return value;
       }
   
       // List<E>: Danh sách chứa các phần tử kiểu E
       // Phương thức trả về kiểu E.
       public static E GetFirstElement<E>(List<E> list, E defaultValue)
       {
           if (list == null || list.Count == 0)
           {
               return defaultValue;
           }
           E first = list.ElementAt(0);
           return first;
       }

   }

}
Ví dụ sử dụng phương thức generics:
MyUtilsDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class MyUtilsDemo
   {

       public static void Main(string[] args)
       {

           // K = int: Phone
           // V = string: Name
           KeyValue<int, string> entry1 = new KeyValue<int, String>(12000111, "Tom");
           KeyValue<int, string> entry2 = new KeyValue<int, String>(12000112, "Jerry");

           // (K = int).
           int phone = MyUtils.GetKey(entry1);
           Console.WriteLine("Phone = " + phone);

           // Một danh sách chứa các phần tử kiểu KeyValue<int,string>.
           List<KeyValue<int, string>> list = new List<KeyValue<int, string>>();

           // Thêm phần tử vào danh sách.
           list.Add(entry1);
           list.Add(entry2);

           KeyValue<int, string> firstEntry = MyUtils.GetFirstElement(list, null);

           if (firstEntry != null)
           {
               Console.WriteLine("Value = " + firstEntry.GetValue());
           }

           Console.Read();
       }

   }

}
Chạy ví dụ:

3- Khởi tạo đối tượng Generic

Đôi khi bạn muốn khởi tạo một đối tượng Generic:
public void DoSomething<T>()
{

     // Khởi tạo đối tượng Generic
     T t = new T(); // Error

}
Nguyên nhân lỗi ở trên là do kiểu tham số T không chắc chắn nó có cấu tử T(), vì vậy bạn cần phải thêm giàng buộc when T : new(). Hãy xem ví dụ dưới đây:
GenericInitializationExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class GenericInitializationExample
   {

 
       // Tham số T phải là kiểu có cấu tử mặc định.
       public void DoSomeThing<T>()
           where T : new()
       {
           T t = new T();
       }
 
       // Tham số K phải là kiểu có cấu tử mặc định
       // và mở rộng từ class KeyValue.
       public void ToDoSomeThing<K>()
           where K: KeyValue<K,string>, new( )
       {
           K key = new K();
       }


       public T DoDefault<T>()
       {
 
           // Trả về null nếu T là kiểu tham số.
           // Hoặc 0 nếu T là kiểu số.
           return default(T);
       }
   }

}

4- Mảng Generic

Trong C# bạn có thể khai báo một mảng Generics:
// Khởi tạo một mảng.

T[] myArray = new T[10];
GenericArrayExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class GenericArrayExample
   {

       public static T[] FilledArray<T>(T value, int count)
       {
           T[] ret = new T[count];
           for (int i = 0; i < count; i++)
           {
               ret[i] = value;
           }
           return ret;
       }

       public static void Main(string[] args)
       {

           string value = "Hello";

           string[] filledArray = FilledArray<string>(value, 10);

           foreach (string s in filledArray)
           {
               Console.WriteLine(s);
           }
       }
   }

}