Generics trong C#

Generics được giới thiệu trong C# 2.0. Generics cho phép bạn định nghĩa một lớp với trình giữ chỗ cho kiểu dữ liệu của trường, phương thức, tham số, v.v ... Generics thay thế các trình giữ chỗ này bằng một số kiểu dữ liệu cụ thể tại thời điểm biên dịch.

Một lớp generic có thể được định nghĩa bằng cách sử dụng cặp dấu <>. Ví dụ sau đây là một lớp generic đơn giản với biến thành viên , phương thức và thuộc tính generic.

class MyGenericClass<T>
{
    private T genericMemberVariable;
    
    public MyGenericClass(T value)
    {
        genericMemberVariable = value;
    }

    public T GenericMethod(T genericParameter)
    {
        Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(), genericParameter);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariable);
            
        return genericMemberVariable;
    }
    
    public T GenericProperty { get; set; }
}

Như bạn có thể thấy trong đoạn mã trên, MyGenericClass có kèm theo <T> ở cuối. Cặp dấu <> chỉ ra rằng MyGenericClass là một lớp generic và kiểu dữ liệu cơ bản sẽ được xác định sau, bây giờ xem nó là T . Bạn có thể sử dụng bất kỳ ký tự hoặc từ thay vì dùng T.

Bây giờ, trình biên dịch gán kiểu dữ liệu dựa trên kiểu dữ liệu được người gọi truyền vào khi khởi tạo một lớp. Ví dụ sau sử dụng kiểu dữ liệu int để khởi tạo:

var instance = new MyGenericClass<int>(10);

int val = instance.GenericMethod(200);

Đây là kết quả khi biên dịch và chạy chương trình:

Parameter type: int, value: 200
Return type: int, value: 10

Hình dưới đây minh họa cách trình biên dịch sẽ thay thế T bằng int trong MyGenericClass.

Generic type trong C#

Lớp MyGenericClass<int> sẽ được biên dịch, như ví dụ dưới đây.

class MyGenericClass
{
    private int genericMemberVariable;

    public MyGenericClass(int value)
    {
        GenericProperty = value;
    }

    public int GenericMethod(int genericParameter)
    {
        Console.WriteLine("Parameter type: {0}, value: {1}", typeof(int).ToString(), genericParameter);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(int).ToString(), genericMemberVariable);

        return genericMemberVariable;
    }
    
    public int GenericProperty { get; set; }
}

Bạn có thể sử dụng bất kỳ kiểu dữ liệu nào khi khởi tạo MyGenricClass. Ví dụ sau sử dụng kiểu dữ liệu string.

var instance = new MyGenericClass<string>("Hello Generic World");

instance.GenericProperty = "This is a generic property example.";
string result = instance.GenericMethod("Generic Parameter");

Đây là kết quả khi biên dịch và chạy chương trình:

Parameter type: string, value: Generic Parameter
Return type: string, value: Hello Generic World

Lớp generic là Lớp cơ sở

Khi kế thừa từ một lớp generic, bạn cần phải chỉ định kiểu dữ liệu cho lớp cơ sở như dưới đây.

class MyDerivedClass : MyGenericClass<string>
{ 
    //implementation
}

Tuy nhiên, nếu bạn muốn lớp dẫn xuất là lớp generic thì không cần chỉ định kiểu dữ liệu cho lớp generic cơ sở.

class MyDerivedClass<U> : MyGenericClass<U>
{ 
    //implementation
}

Nếu lớp generic cơ sở có các ràng buộc, lớp dẫn xuất phải sử dụng các ràng buộc tương tự .

class MyGenericClass<T> where T: class 
{
    // Implementation 
}

class MyDerivedClass<U> : MyGenericClass<U> where U: class
{ 
    //implementation
}

Generic delegate

Delegate định nghĩa chữ ký của phương thức mà nó có thể gọi. Một generic delegate có thể được định nghĩa giống như một delegate thông thương nhưng với kiểu dữ liệu generic.

Ví dụ sau minh họa generic delegate:

class Program
{
    public delegate T Add<T>(T param1, T param2);

    static void Main(string[] args)
    {
        Add<int> sum = AddNumber;
        Console.WriteLine(sum(10, 20));

        Add<string> concate = Concate;
        Console.WriteLine(concate("Hello","World!!"));
        
        Console.ReadKey();
    }

    public static int AddNumber(int val1, int val2)
    {
        return val1 + val2;
    }

    public static string Concate(string str1, string str2)
    {
        return str1 + str2;
    }
}

Đây là kết quả khi biên dịch và chạy chương trình:

30
Hello World!!

Trong ví dụ trên, thêm đại biểu là chung. Trong phương thức Main (), nó đã xác định thêm đại biểu của tổng biến kiểu int. Vì vậy, nó có thể trỏ đến phương thức AddNumber () có tham số kiểu int. Một biến khác của add ủy nhiệm sử dụng kiểu chuỗi, vì vậy nó có thể trỏ đến phương thức Concate. Theo cách này, bạn có thể sử dụng các đại biểu chung cho các phương thức khác nhau của các loại tham số khác nhau.

Lưu ý: Một đại biểu chung có thể trỏ đến các phương thức với các loại tham số khác nhau. Tuy nhiên, số lượng tham số nên giống nhau.

Generics có thể được áp dụng cho những điều sau đây:

  • Giao diện
  • Lớp trừu tượng
  • Lớp học
  • phương pháp
  • Phương pháp tĩnh
  • Bất động sản
  • Biến cố
  • Đại biểu
  • Nhà điều hành

Ưu điểm của Generics

  • Tăng khả năng sử dụng lại mã.
  • Chung là loại an toàn. Bạn nhận được lỗi thời gian biên dịch nếu bạn cố gắng sử dụng một loại dữ liệu khác với loại dữ liệu được chỉ định trong định nghĩa.
  • Generic có lợi thế về hiệu suất vì nó loại bỏ các khả năng của quyền anh và unboxing.

Những điểm cần nhớ:

  • Generics biểu thị với khung thiên thần <>.
  • Trình biên dịch áp dụng loại được chỉ định cho generic trong thời gian biên dịch.
  • Generics có thể được áp dụng cho giao diện, lớp abstrct, phương thức, phương thức tĩnh, thuộc tính, sự kiện, ủy nhiệm và toán tử.
  • Generics thực hiện nhanh hơn bằng cách không tập đấm bốc & unboxing.

Những ràng buộc trong Generics

C # bao gồm các ràng buộc để chỉ định loại loại giữ chỗ với lớp chung được cho phép. Nó sẽ đưa ra một lỗi thời gian biên dịch nếu bạn cố gắng khởi tạo một lớp chung bằng cách sử dụng một loại giữ chỗ không được cho phép bởi một ràng buộc. Ví dụ: nếu các ràng buộc chung chỉ định rằng chỉ có thể sử dụng loại tham chiếu với lớp chung thì bạn không thể sử dụng loại giá trị để tạo một đối tượng của loại chung.

Các ràng buộc có thể được áp dụng bằng cách sử dụng từ khóa where . Trong ví dụ sau, MyGenericClass chỉ định các ràng buộc mà chỉ một loại tham chiếu có thể được sử dụng với MyGenericClass. Điều này có nghĩa là chỉ một lớp có thể là một loại giữ chỗ chứ không phải các kiểu nguyên thủy, struct, v.v.

class MyGenericClass<T> where T: class
{
    private T genericMemberVariable;

    public MyGenericClass(T value)
    {
        genericMemberVariable = value;
    }

    public T genericMethod(T genericParameter)
    {
        Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(),genericParameter);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariable);
            
        return genericMemberVariable;
    }

    public T genericProperty { get; set; }
}

Vì vậy, bây giờ, bạn không thể sử dụng int làm loại giữ chỗ. Sau đây sẽ cung cấp một lỗi thời gian biên dịch.

// error compile time
MyGenericClass<int> intGenericClass = new MyGenericClass<int>(10);

Chuỗi hoặc bất kỳ loại lớp nào là một loại hợp lệ vì nó là một loại tham chiếu.

MyGenericClass<string> strGenericClass = new MyGenericClass<string>("Hello World");

MyGenericClass<Student> strGenericClass = new MyGenericClass<Student>(new Student());

Bảng sau liệt kê các loại ràng buộc chung.

Ràng buộc Miêu tả
where T : class Kiểu dữ liệu phải là kiểu tham chiếu.
where T : struct Kiểu dữ liệu phải là kiểu giá trị.
where T : new() Kiểu dữ liệu phải có phương thức khởi tạo public không có tham số.
where T : <base class name> Kiểu dữ liệu phải là lớp cơ sở hoặc lớp kế thừa từ lớp cơ sở được chỉ định.
where T : <interface name> Kiểu dữ liệu phải interface hoặc lớp triển khai thực hiện giao diện được chỉ định.
where T : U Kiểu dữ liệu được cung cấp cho T phải là kiểu dữ liệu của đối số được cung cấp cho U hoặc lớp kế thừa từ đối số được cung cấp cho U.

Nhiều ràng buộc:

Một lớp chung có thể có nhiều ràng buộc như dưới đây.

class MyGenericClass<T, U> where T: class where U:struct
{
    ...
}

Hạn chế về các phương pháp chung

Bạn cũng có thể áp dụng các ràng buộc trên các phương thức chung.

class MyGenericClass<T> where T: class 
{
    public T genericMethod<U>(T genericParameter, U anotherGenericType) where U: struct
    {
        Console.WriteLine("Generic Parameter of type {0}, value {1}", typeof(T).ToString(),genericParameter);
        Console.WriteLine("Return value of type {0}, value {1}", typeof(T).ToString(), genericMemberVariable);
            
        return genericMemberVariable;
    }        
}

Vì vậy, các ràng buộc có thể được áp dụng trên các loại chung.

Những điểm cần nhớ:

  • Các ràng buộc chỉ định các kiểu dữ liệu được phép sử dụng với generic.
  • Các ràng buộc có thể được áp dụng bằng cách sử dụng từ khóa where.
  • Sáu kiểu dữ liệu có thể được áp dụng ràng buộc: class, struct, new(), base class name, interface và kiểu dữ liệu dẫn xuất.
  • Có thể được áp dụng nhiều ràng buộc một lúc.
Lập Trình C#Lập Trình C# Cơ Bản
Bài Viết Liên Quan:
int[] và int[,] trong C#: Ai nhanh hơn
Trung Nguyen 10/10/2020
int[] và int[,] trong C#: Ai nhanh hơn

Hiểu được sự khác biệt giữa các loại mảng trong C# sẽ giúp bạn chọn cấu trúc dữ liệu chính xác cho mọi trường hợp.

Struct và class trong C#: Ai nhanh hơn
Trung Nguyen 09/10/2020
Struct và class trong C#: Ai nhanh hơn

Trong bài viết này, tôi sẽ so sánh sự khác biệt về hiệu suất giữa struct và class trong C#: Ai nhanh hơn.

Best practice cho performance trong C#
Trung Nguyen 03/10/2020
Best practice cho performance trong C#

Mục tiêu của bài viết này là cung cấp một danh sách không đầy đủ các code mẫu cần tránh, vì chúng rủi ro hoặc performance kém.

Đọc ghi file (File I/O) trong C#
Trung Nguyen 26/04/2020
Đọc ghi file (File I/O) trong C#

Hướng dẫn này sẽ giúp bạn tìm hiểu về đọc ghi file (File I/O) trong C# và sử dụng các lớp tiện ích để đọc ghi file.