openplanning

Hướng dẫn và ví dụ C# Generics

  1. Lớp Generics
  2. Thừa kế lớp Generics
  3. Interface Generics
  4. Sử dụng Generic với Exception
  5. Phương thức generics
  6. Khởi tạo đối tượng Generic
  7. Mảng Generic

1. Lớp Generics

Ví dụ dưới đây định nghĩa ra một lớp generics. KeyValue là một lớp 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 lớp 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 lớp này bạn phải xác định kiểu tham số cụ thể.
Hãy xem ví dụ sử dụng lớp 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ụ:
Phone = 12000111 / name = Tom

2. Thừa kế lớp Generics

Một lớp mở rộng từ một lớp 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
{ 
    // Lớp này mở rộng từ lớp KeyValue<K,V>
    // Và chỉ định rõ kiểu cho 2 tham số 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 rằng kiểu trả về này là int.
            int phone = entry.GetKey();

            // C# hiểu kiểu trả về này 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
{ 
    // Lớp này mở rộng từ lớp KeyValue<K,V>
    // Xác định rõ kiểu tham số K là String.
    // Giữ nguyên 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 lớp StringAndValueEntry:
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
{ 
    // Lớp này mở rộng từ lớp 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 GetInfo(I info)
        {
            this.info = info;
        } 
    } 
}

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 lớp thực hiện (implements) 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;
       }
   }
}

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.
            } 
            // Invalid (Unknown parameter K) *********** 
            // catch (MyException<K> e)
            // {
                // ...            
            // }
            catch (Exception e)
            {
            }
        }
    } 
}

5. Phương thức generics

Một phương thức trong lớp 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 này 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ụ:
Phone = 12000111
Value = Tom

6. 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ó phương thức khởi tạo (constructor) 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 
    {
        // Kiểu T phải là kiểu có Constructor mặc định.
        public void DoSomeThing<T>()
            where T : new()
        {
            T t = new T();
        } 
        // Kiểu K phải có Constructor mặc định
        // và mở rộng từ lớp 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 chiếu (reference type).
            // Hoặc 0 nếu T là kiểu số  (int, float,..) 
            return default(T);
        }
    } 
}

7. 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);
           }
       }
   }
}