hocvietcode.com
  • Trang chủ
  • Học lập trình
    • Lập trình C/C++
    • Lập trình HTML
    • Lập trình Javascript
      • Javascript cơ bản
      • ReactJS framework
      • AngularJS framework
      • Typescript cơ bản
      • Angular
    • Lập trình Mobile
      • Lập Trình Dart Cơ Bản
        • Dart Flutter Framework
    • Cơ sở dữ liệu
      • MySQL – MariaDB
      • Micrsoft SQL Server
      • Extensible Markup Language (XML)
      • JSON
    • Lập trình PHP
      • Lập trình PHP cơ bản
      • Laravel Framework
    • Lập trình Java
      • Java Cơ bản
    • Cấu trúc dữ liệu và giải thuật
    • Lập Trình C# Cơ Bản
    • Machine Learning
  • WORDPRESS
    • WordPress cơ bản
    • WordPress nâng cao
    • Chia sẻ WordPress
  • Kiến thức hệ thống
    • Microsoft Azure
    • Docker
    • Linux
  • Chia sẻ IT
    • Tin học văn phòng
      • Microsoft Word
      • Microsoft Excel
    • Marketing
      • Google Adwords
      • Facebook Ads
      • Kiến thức khác
    • Chia sẻ phần mềm
    • Review công nghệ
    • Công cụ – tiện ích
      • Kiểm tra bàn phím online
      • Kiểm tra webcam online
Đăng nhập
  • Đăng nhập / Đăng ký

Please enter key search to display results.

Home
  • Học lập trình
  • Lập Trình C# Cơ Bản
Kiểu dữ liệu Generics và Iterators trong C# 

Kiểu dữ liệu Generics và Iterators trong C# 

  • 06-12-2023
  • Toanngo92
  • 0 Comments

Generics là cấu trúc dữ liệu cho phép bạn tái sử dụng cùng một đoạn mã cho các loại dữ liệu khác nhau như lớp, giao diện, và những loại khác. Generics thường được sử dụng một cách hiệu quả khi làm việc với mảng và các bộ sưu tập có trình duyệt enumerator. Iterators là các đoạn mã có khả năng lặp lại qua các giá trị của bộ sưu tập.

Chúng ta sẽ tìm hiểu về:

  • Định nghĩa và mô tả về Generics
  • Giải thích cách tạo và sử dụng Generics
  • Giải thích về iterators

Mục lục

  • Generics
  • Namespaces, Classes, and Interfaces cho Generics
  • Namespace System.Collections.ObjectModel
  • Tạo Ra Các Kiểu Generic
  • Các Lợi Ích
  • Tạo và Sử Dụng Generics
  • Các Lớp Generic
  • Ràng buộc trên kiểu tham số
  • Kế thừa từ các Lớp Generic
  • Phương thức Generic
  • Giao diện Generic
  • Ràng buộc Giao diện Generic
  • Generic Delegates trong C#
  • Nạp chồng Phương thức sử dụng kiểu Tham số
  • Ghi đè Phương thức Ảo trong Lớp Generic
  • Iterators trong C#
  • Lợi ích
  • Thực hiện
  • Iterators Generic
  • Thực hiện Iterators Named

Generics

Generics là một dạng cấu trúc dữ liệu tham số hóa có thể làm việc với cả các loại giá trị và tham chiếu. Bạn có thể định nghĩa một lớp, giao diện, cấu trúc, phương thức hoặc một delegate như một kiểu generic trong C#.

Xem xét một chương trình C# sử dụng biến mảng kiểu Object để lưu trữ một tập hợp tên sinh viên. Các tên được đọc từ bảng điều khiển dưới dạng kiểu giá trị và được boxed để có thể lưu trữ mỗi tên như kiểu Object. Trong trường hợp này, trình biên dịch không thể xác minh dữ liệu được lưu trữ so với kiểu dữ liệu của nó vì nó cho phép bạn ép kiểu sang Object. Nếu bạn nhập dữ liệu số, nó sẽ được chấp nhận mà không cần bất kỳ xác minh nào.

Để đảm bảo an toàn về kiểu dữ liệu, C# giới thiệu generics, có nhiều tính năng bao gồm khả năng định nghĩa các mẫu kiểu chung dựa trên đó kiểu có thể được tạo ra sau này.

Namespaces, Classes, and Interfaces cho Generics

Có một số namespace trong .NET Framework hỗ trợ việc tạo và sử dụng generics.

Namespace System.Collections.ObjectModel cho phép bạn tạo các bộ sưu tập generic có tính động và chỉ đọc. Namespace System.Collections.Generic bao gồm các lớp và giao diện cho phép bạn định nghĩa các bộ sưu tập generic tùy chỉnh.

  • Các Lớp (Classes)

Namespace System.Collections.Generic bao gồm các lớp cho phép bạn tạo các bộ sưu tập an toàn về kiểu dữ liệu. Bảng bên dưới liệt kê một số lớp phổ biến của namespace System.Collections.Generic.

LớpMô tả
ComparerLà một lớp trừu tượng cho phép bạn tạo một bộ sưu tập generic bằng cách thực hiện các chức năng của giao diện Comparer.
Dictionary.KeyCollectionChứa các khóa có mặt trong thể hiện của lớp Dictionary.
Dictionary.ValueCollectionChứa các giá trị có mặt trong thể hiện của lớp Dictionary.
EqualityComparerLà một lớp trừu tượng cho phép bạn tạo một bộ sưu tập generic bằng cách thực hiện các chức năng của giao diện EqualityComparer.
Các Lớp trong Namespace System.Collections.Generic
  • Giao diện (Interfaces)

Namespace System.Collections.Generic bao gồm các giao diện cho phép bạn tạo ra các bộ sưu tập an toàn về kiểu dữ liệu.

Bảng bên dưới liệt kê danh sách các giao diện của Namespace System.Collections.Generic

Giao diệnMô tả
IComparerĐịnh nghĩa phương thức generic Compare() so sánh các giá trị trong bộ sưu tập
IEnumerableĐịnh nghĩa phương thức generic GetEnumerator() duyệt qua một bộ sưu tập
IEqualityComparerBao gồm các phương thức kiểm tra sự bằng nhau giữa hai đối tượng

Namespace System.Collections.ObjectModel

Namespace System.Collections.ObjectModel bao gồm các lớp được sử dụng để tạo ra các bộ sưu tập generic tùy chỉnh.

Bảng bên dưới hiển thị các lớp được chứa trong Namespace System.Collections.ObjectModel

LớpMô tả
Collection<T>Cung cấp lớp cơ sở cho các bộ sưu tập generic
Dictionary<TKey, TValue>Cung cấp một lớp trừu tượng cho một bộ sưu tập trong đó các khóa liên kết với các giá trị
ReadOnlyCollection<T>Là một lớp cơ sở generic chỉ đọc ngăn chặn việc sửa đổi bộ sưu tập

Đoạn mã bên dưới minh họa việc sử dụng lớp ReadOnlyCollection.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

class ReadOnly
{
    static void Main(string[] args)
    {
        List<string> objList = new List<string>();
        objList.Add("Francis");
        objList.Add("James");
        objList.Add("Baptista");
        objList.Add("Michael");

        ReadOnlyCollection<string> objReadOnly = new ReadOnlyCollection<string>(objList);
        Console.WriteLine("Values stored in the readonly collection");
        foreach (string str in objReadOnly)
        {
            Console.WriteLine(str);
        }

        Console.WriteLine();
        Console.WriteLine("Total number of elements in the readonly collection: " + objReadOnly.Count);

        if (objList.Contains("Francis"))
        {
            objList.Insert(2, "Peter");

            Console.WriteLine("\nValues stored in the list after modification");
            foreach (string str in objReadOnly)
            {
                Console.WriteLine(str);
            }

            string[] array = new string[objReadOnly.Count * 2];
            objReadOnly.CopyTo(array, 5);

            Console.WriteLine("\nTotal number of values that can be stored in the new array: " + array.Length);
            Console.WriteLine("Values in the new array");

            foreach (string str in array)
            {
                if (str == null)
                {
                    Console.WriteLine("null");
                }
                else
                {
                    Console.WriteLine(str);
                }
            }
        }
    }
}

Trong đoạn mã trên phương thức Main() của lớp ReadOnly tạo một thể hiện của lớp List. Phương thức Add() này chèn các phần tử vào thể hiện của lớp List.

Một thể hiện của lớp ReadOnlyCollection với kiểu string được tạo ra và các phần tử lưu trữ trong thể hiện của lớp List được sao chép sang thể hiện của lớp ReadOnlyCollection. Phương thức Contains() kiểm tra xem lớp List có chứa phần tử cụ thể không. Nếu lớp List chứa phần tử cụ thể, ví dụ ‘Francis’, thì phần tử mới ‘Peter’ sẽ được chèn vào vị trí chỉ định, là vị trí thứ 2. Mã tạo một biến mảng có kích thước gấp đôi của lớp ReadOnlyCollection. Phương thức CopyTo() sao chép các phần tử từ lớp ReadOnlyCollection vào biến mảng từ vị trí thứ năm trở đi.

Hình bên dưới hiển thị kết quả đầu ra của đoạn mã bên trên.

Tạo Ra Các Kiểu Generic

Một khai báo generic luôn chấp nhận một tham số kiểu, là một chỗ trỏ cho kiểu dữ liệu cần thiết. Kiểu được chỉ định chỉ khi một kiểu generic được tham chiếu hoặc được xây dựng như một kiểu trong chương trình.

Quá trình tạo ra một kiểu generic bắt đầu với một định nghĩa kiểu generic chứa các tham số kiểu. Định nghĩa hoạt động như một bản thiết kế. Sau đó, một kiểu generic được xây dựng từ định nghĩa bằng cách chỉ định các kiểu thực tế như các đối số kiểu generic, sẽ thay thế cho các tham số kiểu hoặc những chỗ trỏ.

Các Lợi Ích

Generics đảm bảo an toàn kiểu tại thời điểm biên dịch. Generics cho phép bạn tái sử dụng mã một cách an toàn mà không cần ép kiểu hoặc boxing. Một định nghĩa kiểu generic có thể tái sử dụng với các loại khác nhau nhưng chỉ có thể chấp nhận các giá trị của một kiểu duy nhất vào một thời điểm. Ngoài khả năng tái sử dụng, còn có nhiều lợi ích khác khi sử dụng generics.

Các lợi ích đó là:

  • Cải thiện hiệu suất vì sử dụng bộ nhớ thấp do không cần thực hiện các thao tác ép kiểu (casting) hoặc đóng gói (boxing) khi tạo ra một kiểu generic.
  • Đảm bảo mô hình lập trình kiểu mạnh (strongly-typed programming).
  • Giảm thiểu các lỗi thời gian chạy có thể xảy ra do việc ép kiểu (casting) hoặc đóng gói (boxing).

Tạo và Sử Dụng Generics

Các kiểu generic không chỉ hạn chế trong các lớp mà còn có thể bao gồm cả các giao diện và delegates. Phần này xem xét về các lớp, phương thức, giao diện, delegates generic, và các khái niệm tương tự.

Các Lớp Generic

Các lớp generic xác định các chức năng có thể được sử dụng cho bất kỳ loại dữ liệu nào. Các lớp generic được khai báo với một lệnh khai báo lớp theo sau bởi một tham số kiểu parameter được bao quanh bởi dấu ngoặc nhọn. Trong quá trình khai báo một lớp generic, bạn có thể áp dụng một số hạn chế hoặc ràng buộc cho các tham số kiểu bằng cách sử dụng từ khóa where. Tuy nhiên, việc áp dụng ràng buộc cho các tham số kiểu là tùy chọn.

Do đó, khi tạo ra một lớp generic, bạn cần tổng quát hóa các loại dữ liệu thành tham số kiểu và tùy chọn xác định ràng buộc áp dụng cho tham số kiểu.

Dưới đây là cú pháp được sử dụng để tạo ra một lớp generic:

<access_modifier> class <ClassName> <<type_parameter_list>> [where <type_parameter_constraint_clause>]

trong đó,

  • access_modifier: Xác định phạm vi của lớp generic. Đây là tùy chọn.
  • ClassName: Là tên của lớp generic mới sẽ được tạo ra.
  • <type_parameter>: Được sử dụng như một chỗ trỏ cho kiểu dữ liệu thực tế.
  • type_parameter_constraint_clause: Một lớp hoặc giao diện tùy chọn được áp dụng cho tham số kiểu bằng từ khóa where.

Đoạn mã bên dưới tạo ra một lớp generic có thể được sử dụng cho bất kỳ loại dữ liệu cụ thể nào.

using System;
using System.Collections.Generic;

class General<T>
{
    T[] values;
    int _counter = 0;

    public General(int max)
    {
        values = new T[max];
    }

    public void Add(Tval)
    {
        if (_counter < values.Length)
        {
            values[_counter] = val;
            _counter++;
        }
    }

    public void Display()
    {
        Console.WriteLine("Constructed Class of type: " + typeof(T));
        Console.WriteLine("Values stored in the object of constructed class are: ");
        for (int i = 0; i < values.Length; i++)
        {
            Console.WriteLine(values[i]);
        }
    }
}

class Students
{
    static void Main(string[] args)
    {
        General<string> objGeneral = new General<string>(3);
        objGeneral.Add("John");
        objGeneral.Add("Patrick");
        objGeneral.Display();

        General<int> objGeneral2 = new General<int>(2);
        objGeneral2.Add(200);
        objGeneral2.Add(35);
        objGeneral2.Display();
    }
}

Trong đoạn mã trên, một định nghĩa lớp generic cho General được tạo ra, chấp nhận một tham số kiểu T. Lớp generic khai báo một hàm tạo có tham số với giá trị int. Phương thức Add() nhận tham số cùng loại với lớp generic. Phương thức Display() hiển thị kiểu giá trị được chỉ định bởi kiểu tham số và các giá trị do người dùng cung cấp thông qua đối tượng. Phương thức Main() của lớp Students tạo một thể hiện của lớp generic General bằng cách cung cấp giá trị tham số kiểu string và giá trị total cần lưu trữ là 3. Thể hiện này gọi phương thức Add() chứa giá trị là tên của sinh viên. Các tên này được hiển thị bằng cách gọi phương thức Display(). Sau đó, một đối tượng khác được tạo ra với một loại dữ liệu khác, int, dựa trên cùng một định nghĩa lớp. Định nghĩa lớp là generic, chúng ta không cần phải thay đổi mã hiện tại, mà có thể tái sử dụng cùng một mã cho một loại dữ liệu int. Do đó, sử dụng cùng một định nghĩa lớp generic, chúng ta có thể tạo ra hai danh sách dữ liệu khác nhau.

Kết quả:

Constructed Class is of type: System String
Values stored in the object of constructed class are:
John
Patrick
Constructed Class is of type: System.Int32
Values stored in the object of constructed class are:
200
35

Ghi chú: Các lớp generic có thể được lồng trong các lớp generic khác hoặc không. Tuy nhiên, bất kỳ lớp nào lồng trong một lớp generic đều là một lớp generic vì các kiểu tham số của lớp bên ngoài được cung cấp cho lớp lồng.

Ràng buộc trên kiểu tham số

Bạn có thể áp dụng ràng buộc trên kiểu tham số trong quá trình khai báo một kiểu generic. Ràng buộc là một hạn chế được áp dụng lên kiểu dữ liệu của kiểu tham số. Ràng buộc được chỉ định bằng từ khóa where. Chúng được sử dụng khi người lập trình muốn hạn chế các loại dữ liệu của kiểu tham số để đảm bảo tính nhất quán và đáng tin cậy của dữ liệu trong một bộ sưu tập.

Bảng bên dưới liệt kê các loại ràng buộc có thể áp dụng cho kiểu tham số.

Ràng buộcMô tả
T: structXác định rằng tham số kiểu phải là kiểu giá trị, trừ giá trị null
T: classXác định rằng tham số kiểu phải là kiểu tham chiếu như một lớp, giao diện hoặc delegate
T: new()Xác định rằng tham số kiểu phải có một constructor mà không có bất kỳ tham số nào và có thể được gọi công khai
T: <base class name>Xác định rằng tham số kiểu phải là lớp cha hoặc phải kế thừa từ một lớp cha
T: <interface name>Xác định rằng tham số kiểu phải là một giao diện hoặc phải kế thừa từ một giao diện

Đoạn mã bên dưới tạo ra một lớp generic sử dụng ràng buộc lớp.

using System;
using System.Collections.Generic;

class Employee
{
    string _empName;
    int _empID;

    public Employee(string name, int num)
    {
        _empName = name;
        _empID = num;
    }

    public string Name
    {
        get { return _empName; }
    }

    public int ID
    {
        get { return _empID; }
    }
}

class GenericList<T> where T : Employee
{
    T[] _name = new T[3];
    int _counter = 0;

    public void Add(Tval)
    {
        if (_counter < _name.Length)
        {
            _name[_counter] = val;
            _counter++;
        }
    }

    public void Display()
    {
        for (int i = 0; i < _counter; i++)
        {
            Console.WriteLine(_name[i].Name + ", " + _name[i].ID);
        }
    }
}

class ClassConstraintDemo
{
    static void Main(string[] args)
    {
        GenericList<Employee> objList = new GenericList<Employee>();
        objList.Add(new Employee("John", 100));
        objList.Add(new Employee("James", 200));
        objList.Add(new Employee("Patrick", 300));
        objList.Display();
    }
}

Trong đoạn mã trên, lớp GenericList được tạo ra chấp nhận một tham số kiểu T. Kiểu tham số này được áp dụng với ràng buộc lớp, có nghĩa là kiểu tham số có thể chỉ bao gồm chi tiết của kiểu Employee. Lớp generic tạo một biến mảng với tham số kiểu T, điều này có nghĩa là nó có thể chứa các giá trị kiểu Employee. Phương thức Add() bao gồm tham số ‘val’, sẽ chứa các giá trị được thiết lập trong phương thức Main(). Vì tham số kiểu phải thuộc loại Employee, hàm khởi tạo được gọi khi thiết lập các giá trị trong phương thức Main().

Kết quả:

John, 100
James, 200
Patrich, 300

Ghi chú: Khi bạn sử dụng một loại cụ thể như một ràng buộc, loại được sử dụng như một ràng buộc phải có phạm vi truy cập lớn hơn so với kiểu generic sẽ sử dụng ràng buộc đó.

Kế thừa từ các Lớp Generic

Một lớp generic có thể được kế thừa như bất kỳ lớp không generic nào khác trong C#. Do đó, một lớp generic có thể hoạt động như một lớp cơ sở hoặc một lớp dẫn xuất.

Khi kế thừa một lớp generic trong một lớp generic khác, bạn có thể sử dụng tham số kiểu generic của lớp cơ sở thay vì truyền kiểu dữ liệu của tham số. Tuy nhiên, khi kế thừa một lớp generic trong một lớp không generic, bạn phải cung cấp kiểu dữ liệu của tham số thay vì tham số kiểu generic của lớp cơ sở. Các ràng buộc được áp đặt ở cấp độ lớp cơ sở phải được bao gồm trong lớp generic dẫn xuất.

Hình bên dưới hiển thị một lớp generic như là một lớp cơ sở.

Cú pháp dưới đây được sử dụng để kế thừa một lớp generic từ một lớp generic hiện có:

<access_modifier>class<<BaseClass><<generic type parameter>>{}
<access_modifier>class<DerivedClass>:<BaseClass><<generic type parameter>>{}

trong đó:

  • access_modifier: Xác định phạm vi của lớp generic.
  • BaseClass: Là lớp cơ sở generic.
  • <generic_type_parameter>: Là một placeholder cho kiểu dữ liệu được chỉ định.
  • DerivedClass: Là lớp generic dẫn xuất.

Cú pháp dưới đây được dùng để Kế thừa một lớp không generic từ một lớp generic

<access_modifier> class <BaseClass> <<generic_type_parameter>> {}
<access_modifier> class <DerivedClass> : <BaseClass><<type_parameter_value>> {}

trong đó,

<type_parameter_value>: Có thể là một kiểu dữ liệu như int, string, hoặc float.

Phương thức Generic

Các phương thức generic xử lý các giá trị có kiểu dữ liệu chỉ được biết khi truy cập các biến lưu trữ các giá trị này. Một phương thức generic được khai báo với danh sách tham số kiểu generic được bao quanh bởi dấu ngoặc đơn. Việc định nghĩa phương thức với tham số kiểu cho phép bạn gọi phương thức với một kiểu khác nhau mỗi lần. Bạn có thể khai báo một phương thức generic trong các khai báo lớp generic hoặc không generic. Khi bạn khai báo một phương thức generic trong một khai báo lớp generic, thân của phương thức tham chiếu đến các tham số kiểu của cả phương thức và khai báo lớp.

Các từ khóa có thể được sử dụng khi khai báo các phương thức generic:

  • virtual: Các phương thức generic khai báo với từ khóa virtual có thể bị ghi đè trong lớp dẫn xuất.
  • override: Phương thức generic được khai báo với từ khóa override ghi đè phương thức của lớp cơ sở.
  • abstract: Phương thức generic được khai báo với từ khóa abstract chỉ chứa phần khai báo của phương thức, thường được thực hiện trong một lớp dẫn xuất.

Cú pháp sử dụng cho việc khai báo một phương thức generic:

<access_modifier> <return_type> <Method_name> <<type_parameter_list>>()

trong đó,

  • access_modifier: Xác định phạm vi của phương thức.
  • return_type: Xác định kiểu dữ liệu mà phương thức generic sẽ trả về.
  • Method_name: Tên của phương thức generic.
  • <type_parameter_list>: Là một placeholder cho kiểu dữ liệu thực tế.

Đoạn mã bên dưới tạo ra một phương thức generic trong một lớp không generic.

using System;
using System.Collections.Generic;

class SwapNumbers {
    static void Swap<T>(ref T valOne, ref T valTwo) {
        T temp = valOne;
        valOne = valTwo;
        valTwo = temp;
    }

    static void Main(string[] args) {
        int numOne = 23;
        int numTwo = 45;

        Console.WriteLine("Values before swapping: " + numOne + " & " + numTwo);
        Swap<int>(ref numOne, ref numTwo);
        Console.WriteLine("Values after swapping: " + numOne + " & " + numTwo);
    }
}

Trong đoạn mã trên, lớp SwapNumbers bao gồm một phương thức generic Swap() nhận một tham số kiểu T trong dấu ngoặc kép và hai tham số trong ngoặc đơn của kiểu T. Phương thức Swap() tạo ra một biến temp kiểu T được gán giá trị từ biến valOne. Phương thức Main() hiển thị các giá trị được khởi tạo trong các biến và gọi phương thức Swap() bằng cách cung cấp kiểu int trong dấu ngoặc kép. Điều này sẽ thay thế cho tham số kiểu trong định nghĩa phương thức generic và hiển thị các giá trị được hoán đổi trong các biến.

Kết quả:

Values before swapping: 23 & 45
Values after swapping: 45 & 23

Giao diện Generic

Các giao diện generic hữu ích cho các bộ sưu tập generic hoặc các lớp generic đại diện cho các mục trong bộ sưu tập. Bạn có thể sử dụng các lớp generic với các giao diện generic để tránh các hoạt động boxing và unboxing trên các kiểu dữ liệu giá trị.

Các lớp generic có thể thực hiện các giao diện generic bằng cách truyền các tham số cần thiết được chỉ định trong giao diện. Tương tự như các lớp generic, các giao diện generic cũng triển khai tính kế thừa.

Cú pháp để khai báo một giao diện tương tự như cú pháp cho việc khai báo lớp. Dưới đây là cú pháp được sử dụng để tạo ra một giao diện generic:

<access_modifier> interface <InterfaceName> <<type_parameter_list>> [where <type_parameter_constraint_clause>]

trong đó,

  • access_modifier: Xác định phạm vi của giao diện generic.
  • InterfaceName: Là tên của giao diện generic mới.
  • <type_parameter_list>: Được sử dụng như một placeholder cho kiểu dữ liệu thực tế.
  • type_parameter_constraint_clause: Một lớp tùy chọn hoặc một giao diện được áp dụng cho tham số kiểu với từ khóa where.

Đoạn mã bên dưới tạo ra một giao diện generic được thực hiện bởi lớp không generic.

using System;
using System.Collections.Generic;

interface IMaths<T>
{
    T Addition(T valOne, T valTwo);
    T Subtraction(T valOne, T valTwo);
}

class Numbers : IMaths<int>
{
    public int Addition(int valOne, int valTwo)
    {
        return valOne + valTwo;
    }

    public int Subtraction(int valOne, int valTwo)
    {
        if (valOne > valTwo)
        {
            return valOne - valTwo;
        }
        else
        {
            return valTwo - valOne;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        int numOne = 23;
        int numTwo = 45;
        Numbers objInterface = new Numbers();

        Console.Write("Addition of two integer values is: ");
        Console.WriteLine(objInterface.Addition(numOne, numTwo));

        Console.Write("Subtraction of two integer values is: ");
        Console.WriteLine(objInterface.Subtraction(numOne, numTwo));
    }
}

Trong đoạn mã trên, giao diện generic IMaths lấy một tham số kiểu T và khai báo hai phương thức kiểu T. Lớp Numbers thực hiện giao diện IMaths bằng cách cung cấp kiểu int trong dấu ngoặc nhọn và thực hiện hai phương thức được khai báo trong giao diện generic. Phương thức Main() tạo một thể hiện của lớp Numbers và hiển thị phép cộng và phép trừ của hai số.

Kết quả:

Addition of two integer values is: 68
Subtraction of two integer values is: 22

Ràng buộc Giao diện Generic

Bạn có thể chỉ định một giao diện làm ràng buộc trên một tham số kiểu. Điều này cho phép bạn sử dụng các thành viên của giao diện trong lớp generic. Ngoài ra, nó đảm bảo rằng chỉ có các loại thực hiện giao diện được sử dụng.

Bạn cũng có thể chỉ định nhiều giao diện làm ràng buộc trên một tham số kiểu duy nhất. Đoạn mã bên dưới tạo một giao diện generic được sử dụng làm ràng buộc trong một lớp generic.

using System;
using System.Collections.Generic;

interface IDetails
{
    void GetDetails();
}

class Student : IDetails
{
    string _studName;
    int _studID;

    public Student(string name, int num)
    {
        _studName = name;
        _studID = num;
    }

    public void GetDetails()
    {
        Console.WriteLine(_studID + "\t" + _studName);
    }
}

class GenericList<T> where T : IDetails
{
    T[] _values = new T[3];
    int _counter = 0;

    public void Add(T val)
    {
        if (_counter < 3)
        {
            _values[_counter] = val;
            _counter++;
        }
    }

    public void Display()
    {
        for (int i = 0; i < 3; i++)
        {
            _values[i].GetDetails();
        }
    }
}

class InterfaceConstraintDemo
{
    static void Main(string[] args)
    {
        GenericList<Student> objList = new GenericList<Student>();
        objList.Add(new Student("Wilson", 120));
        objList.Add(new Student("Jack", 130));
        objList.Add(new Student("Peter", 140));
        objList.Display();
    }
}

Trong đoạn mã trên, một giao diện IDetails khai báo một phương thức GetDetails(). Lớp Student thực hiện giao diện IDetails. Lớp GenericList được tạo ra, nhận một tham số kiểu T. Tham số kiểu này được áp dụng ràng buộc của giao diện, có nghĩa là tham số kiểu chỉ có thể bao gồm chi tiết của loại IDetails. Phương thức Main() tạo một thể hiện của lớp GenericList bằng cách truyền giá trị tham số kiểu là Student, vì lớp Student thực hiện giao diện IDetails.

Hình bên dưới tạo một giao diện generic.

Generic Delegates trong C#

Delegate là các kiểu tham chiếu gói một tham chiếu tới một phương thức có chữ ký và kiểu trả về. Tương tự như các lớp, giao diện và cấu trúc, phương thức người dùng tự định nghĩa và delegate cũng có thể được khai báo như là generic. Một generic delegate có thể được sử dụng để tham chiếu đến nhiều phương thức trong một lớp với các loại tham số khác nhau. Tuy nhiên, số lượng tham số của delegate và các phương thức tham chiếu phải là giống nhau. Cú pháp để khai báo một generic delegate tương tự như khi khai báo một generic method, nơi danh sách type parameter được chỉ định sau tên của delegate.

Dưới đây là cú pháp được sử dụng để khai báo một generic delegate:

delegate <return_type> <DelegateName> <type parameter list> (<argument_list>);

trong đó,

  • return_type: Xác định kiểu giá trị mà delegate sẽ trả về.
  • DelegateName: Là tên của generic delegate.
  • type parameter list: Được sử dụng như một chỗ đặt dấu giữ chỗ cho kiểu dữ liệu thực tế.
  • argument_list: Xác định danh sách tham số trong delegate.

Đoạn mã bên dưới đây khai báo một generic delegate.

using System;

delegate T DelMath<T>(T val);

class Numbers
{
    static int NumberType(int num)
    {
        if (num % 2 == 0)
            return 1;
        else
            return 0;
    }

    static float NumberType(float num)
    {
        return num % 2.5F;
    }

    public static void Main(string[] args)
    {
        DelMath<int> objDel = NumberType;
        DelMath<float> objDel2 = NumberType;

        Console.WriteLine(objDel(10));
        Console.WriteLine(objDel2(108.756F));
    }
}

Trong đoạn mã trên, một generic delegate được khai báo trong lớp Numbers. Trong phương thức Main() của lớp, một đối tượng của delegate được tạo ra, đang tham chiếu đến phương thức NumberType() và nhận tham số kiểu int. Một giá trị số nguyên được truyền vào phương thức, chỉ hiển thị giá trị nếu nó là số chẵn. Một đối tượng khác của delegate được tạo trong phương thức Main(), đang tham chiếu đến phương thức NumberType() và nhận tham số kiểu Float. Một giá trị Float được truyền vào phương thức, hiển thị phần dư của phép chia. Do đó, generic delegate có thể được sử dụng cho các phương thức quá tải.

Hình bên dưới khai báo một generic delegate.

Nạp chồng Phương thức sử dụng kiểu Tham số

Các phương thức của một lớp generic sử dụng tham số kiểu generic có thể được nạp chồng. Người lập trình có thể nạp chồng các phương thức sử dụng tham số kiểu bằng cách thay đổi kiểu hoặc số lượng tham số. Tuy nhiên, sự khác biệt về kiểu không dựa trên tham số kiểu generic, mà dựa trên kiểu dữ liệu của tham số được truyền.

Đoạn mã bên dưới thể hiện cách nạp chồng các phương thức sử dụng kiểu tham số.

using System;
using System.Collections;
using System.Collections.Generic;

class General<T, U>
{
    T _valOne;
    U _valTwo;

    public void AcceptValues(T item)
    {
        _valOne = item;
    }

    public void AcceptValues(U item)
    {
        _valTwo = item;
    }

    public void Display()
    {
        Console.Write(_valOne + "\t" + _valTwo);
    }
}

class MethodOverload
{
    static void Main(string[] args)
    {
        General<int, string> objGenOne = new General<int, string>();
        objGenOne.AcceptValues(10);
        objGenOne.AcceptValues("Smith");

        Console.WriteLine("ID\tName\tDesignation\tSalary");
        objGenOne.Display();

        General<string, float> objGenTwo = new General<string, float>();
        objGenTwo.AcceptValues("Mechanic");
        objGenTwo.AcceptValues(2500);

        Console.Write("\t");
        objGenTwo.Display();

        Console.WriteLine();
    }
}


Trong đoạn mã trên, lớp General có hai phương thức nạp chồng với các tham số kiểu khác nhau. Trong phương thức Main(), một thể hiện của lớp General được tạo ra. Lớp được khởi tạo bằng cách xác định kiểu dữ liệu cho các tham số generic T và U là string và int tương ứng. Các phương thức nạp chồng được gọi bằng cách xác định các giá trị phù hợp. Các phương thức này lưu các giá trị này vào các biến tương ứng được định nghĩa trong lớp General. Các giá trị này chỉ ra ID và tên của nhân viên.

Một thể hiện khác của lớp General được tạo ra, xác định kiểu dữ liệu mà lớp có thể chứa là string và float. Các phương thức nạp chồng được gọi bằng cách xác định các giá trị phù hợp. Các phương thức này lưu các giá trị này vào các biến tương ứng được định nghĩa trong lớp General. Các giá trị này chỉ ra chức vụ và mức lương của nhân viên.

Hình bên dưới hiển thị đầu ra của việc sử dụng các phương thức nạp chồng với các tham số kiểu.

Ghi đè Phương thức Ảo trong Lớp Generic

Các phương thức trong các lớp generic có thể được ghi đè giống như trong bất kỳ lớp không generic nào. Để ghi đè một phương thức trong lớp generic, phương thức trong lớp cơ sở phải được khai báo là virtual, và phương thức này có thể được ghi đè trong lớp dẫn xuất bằng cách sử dụng từ khóa override.

Đoạn mã bên dưới thể hiện cách ghi đè các phương thức ảo của một lớp generic.

using System;
using System.Collections;
using System.Collections.Generic;

class GeneralList<T>
{
    protected T ItemOne;

    public GeneralList(T valOne)
    {
        ItemOne = valOne;
    }

    public virtual T GetValue()
    {
        return ItemOne;
    }
}

class Student<T> : GeneralList<T>
{
    public T Value;

    public Student(T valOne, T valTwo) : base(valOne)
    {
        Value = valTwo;
    }

    public override T GetValue()
    {
        Console.Write(base.GetValue() + "\t\t");
        return Value;
    }
}

class StudentList
{
    public static void Main()
    {
        Student<string> objStudent = new Student<string>("Patrick", "Male");

        Console.WriteLine("Name\t\tSex");
        Console.WriteLine(objStudent.GetValue());
    }
}

Trong đoạn mã trên, lớp GeneralList bao gồm một constructor gán tên của sinh viên. Phương thức GetValue() của lớp GeneralList được ghi đè trong lớp Student. Constructor của lớp Student gọi constructor của lớp cơ sở bằng từ khóa base và gán giới tính của sinh viên được chỉ định. Phương thức GetValue() của lớp dẫn xuất trả về giới tính của sinh viên. Tên của sinh viên được lấy bằng cách sử dụng từ khóa base để gọi phương thức GetValue() của lớp cơ sở. Lớp StudentList tạo một thể hiện của lớp Student. Thể hiện này gọi phương thức GetValue() của lớp dẫn xuất, sau đó gọi phương thức GetValue() của lớp cơ sở bằng cách sử dụng từ khóa base.

Hình bên dưới hiển thị đầu ra khi sử dụng các phương thức ảo được ghi đè trong lớp generic.

Iterators trong C#

Hãy tưởng tượng một tình huống trong đó một người đang cố gắng ghi nhớ một cuốn sách gồm 100 trang. Để hoàn thành nhiệm vụ, người đó phải lặp lại từng trang trong số 100 trang đó.

Tương tự như người đó lặp lại từng trang, ở trong ngôn ngữ lập trình C#, iterator được sử dụng để duyệt qua một danh sách giá trị hoặc một bộ sưu tập. Đó là một đoạn mã sử dụng vòng lặp Foreach để tham chiếu đến một bộ sưu tập các giá trị theo thứ tự tuần tự. Ví dụ, hãy xem xét một tập hợp các giá trị cần được sắp xếp. Để triển khai logic một cách thủ công, lập trình viên có thể lặp qua từng giá trị theo thứ tự bằng iterators để so sánh chúng.

Hình bên dưới minh họa những tương tự này.

Một iterator không phải là thành viên dữ liệu mà là một cách để truy cập các thành viên. Nó có thể là một phương thức, một truy cập được nhận hoặc một toán tử cho phép bạn duyệt qua các giá trị trong một bộ sưu tập. Iterator chỉ định cách giá trị được tạo ra khi câu lệnh foreach truy cập các phần tử trong một bộ sưu tập. Chúng theo dõi các phần tử trong bộ sưu tập, để bạn có thể truy xuất các giá trị này nếu cần thiết.

Ví dụ, hãy xem xét một mảng có sáu phần tử, nơi iterator có thể trả về tất cả các phần tử trong mảng một cách tuần tự.

Lợi ích

Đối với một lớp hoạt động giống như một bộ sưu tập, việc sử dụng iterators để duyệt qua các giá trị của bộ sưu tập với câu lệnh foreach là lựa chọn tốt. Bằng cách làm điều này, bạn có thể nhận được các lợi ích sau:

  • Iterators cung cấp một cách duyệt qua các giá trị của bộ sưu tập đơn giản và nhanh chóng.
  • Iterators giảm độ phức tạp của việc cung cấp một bộ liệt kê cho một bộ sưu tập.
  • Iterators có thể trả về một lượng lớn giá trị.
  • Iterators có thể được sử dụng để đánh giá và trả về chỉ các giá trị cần thiết.
  • Iterators có thể trả về giá trị mà không tiêu tốn bộ nhớ bằng cách tham chiếu từng giá trị trong danh sách.

Thực hiện

Iterator có thể được tạo ra bằng cách triển khai phương thức GetEnumerator() trả về một tham chiếu của giao diện IEnumerator. Khối iterator sử dụng từ khóa yield để cung cấp các giá trị cho thể hiện của enumerator hoặc kết thúc quá trình lặp. Lệnh yield return trả về các giá trị, trong khi lệnh yield break kết thúc quá trình lặp. Khi điều khiển chương trình đến lệnh yield return, vị trí hiện tại được lưu trữ và lần gọi iterator tiếp theo, thực thi bắt đầu từ vị trí đã lưu.

Đoạn mã bên dưới mô tả việc sử dụng iterators để duyệt qua các giá trị của một bộ sưu tập.

using System;
using System.Collections;

class Department : IEnumerable
{
    string[] departmentNames = { "Marketing", "Finance", "Information Technology", "Human Resources" };

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < departmentNames.Length; i++)
        {
            yield return departmentNames[i];
        }
    }

    static void Main(string[] args)
    {
        Department objDepartment = new Department();
        Console.WriteLine("Department Names");
        Console.WriteLine();
        foreach (string str in objDepartment)
        {
            Console.WriteLine(str);
        }
    }
}

Trong đoạn mã trên, lớp Department triển khai giao diện IEnunerable. Lớp Department bao gồm một biến mảng lưu trữ các tên phòng ban và một phương thức GetEnunerator(), chứa vòng lặp for. Vòng lặp for trả về các tên phòng ban tại mỗi vị trí chỉ mục trong biến mảng. Khối mã này trong phương thức GetEnumcrator() bao gồm iterator trong ví dụ này. Phương thức Main() tạo một thể hiện của lớp Department và chứa một vòng lặp foreach hiển thị các tên phòng ban.

Hình bên dưới mô tả việc sử dụng iterators.

Ghi chú: Khi trình biên dịch C# gặp một iterator, nó mặc định gọi các phương thức Current, MoveNext, và Dispose từ giao diện IEnumerable. Những phương thức này được sử dụng để duyệt qua dữ liệu trong bộ sưu tập.

Iterators Generic

C# cho phép các lập trình viên tạo ra các iterator generic. Các iterator generic được tạo ra bằng cách trả về một đối tượng từ giao diện Enumerator<T> hoặc IEnumerable<T> generic. Chúng được sử dụng để lặp qua các giá trị của bất kỳ loại giá trị nào.

Đoạn mã bên dưới mô tả cách tạo ra một iterator generic để duyệt qua các giá trị của bất kỳ loại nào.

using System;
using System.Collections.Generic;

class GenericDepartment<T>{
    T[] items;

    public GenericDepartment(T[] val)
    {
        items = val;
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (T value in items)
        {
            yield return value;
        }
    }
}

class GenericIterator
{
    static void Main(string[] args)
    {
        string[] departmentNames = { "Marketing", "Finance", "Human Resources", "Information Technology", "HumanResources" };
        GenericDepartment<string> objGeneralName = new GenericDepartment<string>(departmentNames);

        foreach (string val in objGeneralName)
        {
            Console.Write(val + "\t");
        }

        int[] departmentID = { 101, 110, 210, 220 };
        GenericDepartment<int> objGeneralID = new GenericDepartment<int>(departmentID);

        Console.WriteLine();
        foreach (int val in objGeneralID)
        {
            Console.Write(val + "\t\t");
        }
        Console.WriteLine();
    }
}

Hình bên dưới tạo một iterator generic

Trong đoạn mã trên, lớp generic GenericDepartment được tạo với tham số kiểu generic T. Lớp này khai báo một biến mảng và bao gồm một hàm tạo có tham số gán các giá trị cho biến mảng này. Trong lớp generic GenericDepartment, phương thức GetEnumerator() trả về một kiểu generic của giao diện IEnumerator. Phương thức này trả về các phần tử được lưu trữ trong biến mảng, sử dụng câu lệnh yield. Trong lớp GenericIterator, một thể hiện của lớp GenericDepartment được tạo ra tham chiếu đến các tên phòng ban khác nhau trong mảng. Một đối tượng khác của lớp GenericDepartment được tạo ra, tham chiếu đến các mã phòng ban khác nhau trong mảng.

Thực hiện Iterators Named

Một cách khác để tạo iterators là bằng cách tạo ra một phương thức, có kiểu trả về là giao diện IEnumerable. Điều này được gọi là iterator named. Iterator có tên có thể chấp nhận các tham số có thể được sử dụng để quản lý điểm bắt đầu và kết thúc của một vòng lặp foreach. Kỹ thuật linh hoạt này cho phép bạn lấy các giá trị cần thiết từ bộ sưu tập.

Cú pháp sau tạo ra một iterator named:

<access_modifier> IEnumerable <IteratorName> (<parameter list>) {}

trong đó,

  • access_modifier: Xác định phạm vi của iterator có tên.
  • IteratorName: Là tên của phương thức iterator.
  • parameter list: Xác định không hoặc nhiều tham số để truyền vào phương thức iterator.

Đoạn mã bên dưới thể hiện cách tạo ra một iterator named.

using System;

class NamedIterators {
    string[] cars = { "Ferrari", "Mercedes", "BMW", "Toyota", "Nissan" };

    public IEnumerable GetCarNames() {
        for (int i = 0; i < cars.Length; i++){
            yield return cars[i];
        }
    }

    static void Main(string[] args) {
        NamedIterators objIterator = new NamedIterators();
        foreach (string str in objIterator.GetCarNames()){
            Console.WriteLine(str);
        }
    }
}

Trong đoạn mã trên, lớp NamedIterators bao gồm một biến mảng và một phương thức GetCarNames(), có kiểu trả về là IEnumerable. Vòng lặp for duyệt qua các giá trị trong biến mảng. Phương thức Main() tạo một thể hiện của lớp NamedIterators và thể hiện này được sử dụng trong vòng lặp foreach để hiển thị tên của các xe từ biến mảng.

Kết quả:

Ferrari
Mercedes
BMW
Toyota
Nissan

Bài viết liên quan:

Sắp xếp sủi bọt – Bubble Sort
TypeScript với Kiểu Dữ Liệu Cơ Bản – 3
TypeScript với Kiểu Dữ Liệu Cơ Bản – 2
TypeScript với Kiểu Dữ Liệu Cơ Bản – 1
Typescript cơ bản và cách cài đặt cho người mới
Thực Hành Micro Frontends
Dynamic Component trong Angular
Async Validator trong Angular Form
Reactive Forms Trong Angular (Phần 2)
Reactive Forms Trong Angular (Phần 1)
Template-driven Forms Trong Angular (Phần 2)
Template-driven Forms Trong Angular (Phần 1)

THÊM BÌNH LUẬN Cancel reply

Dịch vụ thiết kế Wesbite

NỘI DUNG MỚI CẬP NHẬT

2. PHÂN TÍCH VÀ ĐẶC TẢ HỆ THỐNG

1. TỔNG QUAN KIẾN THỨC THỰC HÀNH TRIỂN KHAI DỰ ÁN CÔNG NGHỆ THÔNG TIN

Hướng dẫn tự cài đặt n8n comunity trên CyberPanel, trỏ tên miền

Mẫu prompt tạo mô tả chi tiết bối cảnh

Một số cải tiến trong ASP.NET Core, Razor Page, Model Binding, Gabbage collection

Giới thiệu

hocvietcode.com là website chia sẻ và cập nhật tin tức công nghệ, chia sẻ kiến thức, kỹ năng. Chúng tôi rất cảm ơn và mong muốn nhận được nhiều phản hồi để có thể phục vụ quý bạn đọc tốt hơn !

Liên hệ quảng cáo: [email protected]

Kết nối với HỌC VIẾT CODE

© hocvietcode.com - Tech888 Co .Ltd since 2019

Đăng nhập

Trở thành một phần của cộng đồng của chúng tôi!
Registration complete. Please check your email.
Đăng nhập bằng google
Đăng kýBạn quên mật khẩu?

Create an account

Welcome! Register for an account
The user name or email address is not correct.
Registration confirmation will be emailed to you.
Log in Lost your password?

Reset password

Recover your password
Password reset email has been sent.
The email could not be sent. Possible reason: your host may have disabled the mail function.
A password will be e-mailed to you.
Log in Register
×