Nền tảng Kiến thức - Hành trang tới Tương lai
Card image

Chương 3-Bài 10. Xử lý Đọc/Ghi File trong C#

Tác giả: Dương Nguyễn Phú Cường #732
Ngày đăng: Hồi xưa đó
Lượt xem: 94

Workflow FileStream

Giải thích Buffer là gì?

Quá trình đọc file lấy luồng dữ liệu

file is a collection of data stored in a disk with a specific name and a directory path. When a file is opened for reading or writing, it becomes a stream.

The stream is basically the sequence of bytes passing through the communication path. There are two main streams: the input stream and the output stream. The input stream is used for reading data from file (read operation) and the output stream is used for writing into the file (write operation).

C# I/O Classes

The System.IO namespace has various classes that are used for performing numerous operations with files, such as creating and deleting files, reading from or writing to a file, closing a file etc.

The following table shows some commonly used non-abstract classes in the System.IO namespace −

Sr.No. I/O Class & Description
1

BinaryReader

Reads primitive data from a binary stream.

2

BinaryWriter

Writes primitive data in binary format.

3

BufferedStream

A temporary storage for a stream of bytes.

4

Directory

Helps in manipulating a directory structure.

5

DirectoryInfo

Used for performing operations on directories.

6

DriveInfo

Provides information for the drives.

7

File

Helps in manipulating files.

8

FileInfo

Used for performing operations on files.

9

FileStream

Used to read from and write to any location in a file.

10

MemoryStream

Used for random access to streamed data stored in memory.

11

Path

Performs operations on path information.

12

StreamReader

Used for reading characters from a byte stream.

13

StreamWriter

Is used for writing characters to a stream.

14

StringReader

Is used for reading from a string buffer.

15

StringWriter

Is used for writing into a string buffer.

The FileStream Class

The FileStream class in the System.IO namespace helps in reading from, writing to and closing files. This class derives from the abstract class Stream.

You need to create a FileStream object to create a new file or open an existing file. The syntax for creating a FileStream object is as follows −

FileStream <object_name> = new FileStream( <file_name>, <FileMode Enumerator>,
   <FileAccess Enumerator>, <FileShare Enumerator>);

For example, we create a FileStream object F for reading a file named sample.txt as shown −

FileStream F = new FileStream("sample.txt", FileMode.Open, 
   FileAccess.Read, FileShare.Read);
Sr.No. Parameter & Description
1

FileMode

The FileMode enumerator defines various methods for opening files. The members of the FileMode enumerator are −

  • Append − It opens an existing file and puts cursor at the end of file, or creates the file, if the file does not exist.
  • Create − It creates a new file.
  • CreateNew − It specifies to the operating system, that it should create a new file.
  • Open − It opens an existing file.
  • OpenOrCreate − It specifies to the operating system that it should open a file if it exists, otherwise it should create a new file.
  • Truncate − It opens an existing file and truncates its size to zero bytes.
2

FileAccess

FileAccess enumerators have members: ReadReadWrite and Write.

3

FileShare

FileShare enumerators have the following members −

  • Inheritable − It allows a file handle to pass inheritance to the child processes
  • None − It declines sharing of the current file
  • Read − It allows opening the file for readin.
  • ReadWrite − It allows opening the file for reading and writing
  • Write − It allows opening the file for writing

Example

The following program demonstrates use of the FileStream class −

using System;
using System.IO;

namespace FileIOApplication {
   class Program {
      static void Main(string[] args) {
         FileStream F = new FileStream("test.dat", FileMode.OpenOrCreate, 
            FileAccess.ReadWrite);
         
         for (int i = 1; i <= 20; i++) {
            F.WriteByte((byte)i);
         }
         F.Position = 0;
         for (int i = 0; i <= 20; i++) {
            Console.Write(F.ReadByte() + " ");
         }
         F.Close();
         Console.ReadKey();
      }
   }
}

When the above code is compiled and executed, it produces the following result −

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -1

Advanced File Operations in C#

The preceding example provides simple file operations in C#. However, to utilize the immense powers of C# System.IO classes, you need to know the commonly used properties and methods of these classes.

Sr.No. Topic & Description
1 Reading from and Writing into Text filesIt involves reading from and writing into text files. The StreamReader and StreamWriter class helps to accomplish it.
2 Reading from and Writing into Binary filesIt involves reading from and writing into binary files. The BinaryReader and BinaryWriter class helps to accomplish this.
3 Manipulating the Windows file systemIt gives a C# programamer the ability to browse and locate Windows files and directories.

 

C# - Reading from and Writing to Text Files

The StreamReader and StreamWriter classes are used for reading from and writing data to text files. These classes inherit from the abstract base class Stream, which supports reading and writing bytes into a file stream.

The StreamReader Class

The StreamReader class also inherits from the abstract base class TextReader that represents a reader for reading series of characters. The following table describes some of the commonly used methods of the StreamReader class −

Sr.No. Method & Description
1

public override void Close()

It closes the StreamReader object and the underlying stream, and releases any system resources associated with the reader.

2

public override int Peek()

Returns the next available character but does not consume it.

3

public override int Read()

Reads the next character from the input stream and advances the character position by one.

Example

The following example demonstrates reading a text file named Jamaica.txt. The file reads −

Down the way where the nights are gay
And the sun shines daily on the mountain top
I took a trip on a sailing ship
And when I reached Jamaica
I made a stop
using System;
using System.IO;

namespace FileApplication {
   class Program {
      static void Main(string[] args) {
         try {
            // Create an instance of StreamReader to read from a file.
            // The using statement also closes the StreamReader.
            using (StreamReader sr = new StreamReader("c:/jamaica.txt")) {
               string line;

               // Read and display lines from the file until 
               // the end of the file is reached. 
               while ((line = sr.ReadLine()) != null) {
                  Console.WriteLine(line);
               }
            }
         } catch (Exception e) {
            // Let the user know what went wrong.
            Console.WriteLine("The file could not be read:");
            Console.WriteLine(e.Message);
         }
         Console.ReadKey();
      }
   }
}

Guess what it displays when you compile and run the program!

The StreamWriter Class

The StreamWriter class inherits from the abstract class TextWriter that represents a writer, which can write a series of character.

The following table describes the most commonly used methods of this class −

Sr.No. Method & Description
1

public override void Close()

Closes the current StreamWriter object and the underlying stream.

2

public override void Flush()

Clears all buffers for the current writer and causes any buffered data to be written to the underlying stream.

3

public virtual void Write(bool value)

Writes the text representation of a Boolean value to the text string or stream. (Inherited from TextWriter.)

4

public override void Write(char value)

Writes a character to the stream.

5

public virtual void Write(decimal value)

Writes the text representation of a decimal value to the text string or stream.

6

public virtual void Write(double value)

Writes the text representation of an 8-byte floating-point value to the text string or stream.

7

public virtual void Write(int value)

Writes the text representation of a 4-byte signed integer to the text string or stream.

8

public override void Write(string value)

Writes a string to the stream.

9

public virtual void WriteLine()

Writes a line terminator to the text string or stream.

For a complete list of methods, please visit Microsoft's C# documentation.

Example

The following example demonstrates writing text data into a file using the StreamWriter class −

using System;
using System.IO;

namespace FileApplication {
   class Program {
      static void Main(string[] args) {
         string[] names = new string[] {"Zara Ali", "Nuha Ali"};
         
         using (StreamWriter sw = new StreamWriter("names.txt")) {

            foreach (string s in names) {
               sw.WriteLine(s);
            }
         }
         
         // Read and show each line from the file.
         string line = "";
         using (StreamReader sr = new StreamReader("names.txt")) {
            while ((line = sr.ReadLine()) != null) {
               Console.WriteLine(line);
            }
         }
         Console.ReadKey();
      }
   }
}

When the above code is compiled and executed, it produces the following result −

Zara Ali
Nuha Ali

 

Reading from and Writing into Binary files

The BinaryReader and BinaryWriter classes are used for reading from and writing to a binary file.

The BinaryReader Class

The BinaryReader class is used to read binary data from a file. A BinaryReader object is created by passing a FileStream object to its constructor.

The following table describes commonly used methods of the BinaryReaderclass.

Sr.No. Method & Description
1

public override void Close()

It closes the BinaryReader object and the underlying stream.

2

public virtual int Read()

Reads the characters from the underlying stream and advances the current position of the stream.

3

public virtual bool ReadBoolean()

Reads a Boolean value from the current stream and advances the current position of the stream by one byte.

4

public virtual byte ReadByte()

Reads the next byte from the current stream and advances the current position of the stream by one byte.

5

public virtual byte[] ReadBytes(int count)

Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.

6

public virtual char ReadChar()

Reads the next character from the current stream and advances the current position of the stream in accordance with the Encoding used and the specific character being read from the stream.

7

public virtual char[] ReadChars(int count)

Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.

8

public virtual double ReadDouble()

Reads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.

9

public virtual int ReadInt32()

Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.

10

public virtual string ReadString()

Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time.

The BinaryWriter Class

The BinaryWriter class is used to write binary data to a stream. A BinaryWriter object is created by passing a FileStream object to its constructor.

The following table describes commonly used methods of the BinaryWriter class.

Sr.No. Function & Description
1

public override void Close()

It closes the BinaryWriter object and the underlying stream.

2

public virtual void Flush()

Clears all buffers for the current writer and causes any buffered data to be written to the underlying device.

3

public virtual long Seek(int offset, SeekOrigin origin)

Sets the position within the current stream.

4

public virtual void Write(bool value)

Writes a one-byte Boolean value to the current stream, with 0 representing false and 1 representing true.

5

public virtual void Write(byte value)

Writes an unsigned byte to the current stream and advances the stream position by one byte.

6

public virtual void Write(byte[] buffer)

Writes a byte array to the underlying stream.

7

public virtual void Write(char ch)

Writes a Unicode character to the current stream and advances the current position of the stream in accordance with the Encoding used and the specific characters being written to the stream.

8

public virtual void Write(char[] chars)

Writes a character array to the current stream and advances the current position of the stream in accordance with the Encoding used and the specific characters being written to the stream.

9

public virtual void Write(double value)

Writes an eight-byte floating-point value to the current stream and advances the stream position by eight bytes.

10

public virtual void Write(int value)

Writes a four-byte signed integer to the current stream and advances the stream position by four bytes.

11

public virtual void Write(string value)

Writes a length-prefixed string to this stream in the current encoding of the BinaryWriter, and advances the current position of the stream in accordance with the encoding used and the specific characters being written to the stream.

For a complete list of methods, please visit Microsoft C# documentation.

Example

The following example demonstrates reading and writing binary data −

using System;
using System.IO;

namespace BinaryFileApplication {
   class Program {
      static void Main(string[] args) {
         BinaryWriter bw;
         BinaryReader br;
         
         int i = 25;
         double d = 3.14157;
         bool b = true;
         string s = "I am happy";
         
         //create the file
         try {
            bw = new BinaryWriter(new FileStream("mydata", FileMode.Create));
         } catch (IOException e) {
            Console.WriteLine(e.Message + "\n Cannot create file.");
            return;
         }
         
         //writing into the file
         try {
            bw.Write(i);
            bw.Write(d);
            bw.Write(b);
            bw.Write(s);
         } catch (IOException e) {
            Console.WriteLine(e.Message + "\n Cannot write to file.");
            return;
         }
         bw.Close();
         
         //reading from the file
         try {
            br = new BinaryReader(new FileStream("mydata", FileMode.Open));
         } catch (IOException e) {
            Console.WriteLine(e.Message + "\n Cannot open file.");
            return;
         }
         
         try {
            i = br.ReadInt32();
            Console.WriteLine("Integer data: {0}", i);
            d = br.ReadDouble();
            Console.WriteLine("Double data: {0}", d);
            b = br.ReadBoolean();
            Console.WriteLine("Boolean data: {0}", b);
            s = br.ReadString();
            Console.WriteLine("String data: {0}", s);
         } catch (IOException e) {
            Console.WriteLine(e.Message + "\n Cannot read from file.");
            return;
         }
         br.Close();
         Console.ReadKey();
      }
   }
}

When the above code is compiled and executed, it produces the following result −

Integer data: 25
Double data: 3.14157
Boolean data: True
String data: I am happy

 

C# - Windows File System

C# allows you to work with the directories and files using various directory and file related classes such as the DirectoryInfo class and the FileInfoclass.

The DirectoryInfo Class

The DirectoryInfo class is derived from the FileSystemInfo class. It has various methods for creating, moving, and browsing through directories and subdirectories. This class cannot be inherited.

Following are some commonly used properties of the DirectoryInfo class −

Sr.No. Property & Description
1

Attributes

Gets the attributes for the current file or directory.

2

CreationTime

Gets the creation time of the current file or directory.

3

Exists

Gets a Boolean value indicating whether the directory exists.

4

Extension

Gets the string representing the file extension.

5

FullName

Gets the full path of the directory or file.

6

LastAccessTime

Gets the time the current file or directory was last accessed.

7

Name

Gets the name of this DirectoryInfo instance.

Following are some commonly used methods of the DirectoryInfo class −

Sr.No. Method & Description
1

public void Create()

Creates a directory.

2

public DirectoryInfo CreateSubdirectory(string path)

Creates a subdirectory or subdirectories on the specified path. The specified path can be relative to this instance of the DirectoryInfo class.

3

public override void Delete()

Deletes this DirectoryInfo if it is empty.

4

public DirectoryInfo[] GetDirectories()

Returns the subdirectories of the current directory.

5

public FileInfo[] GetFiles()

Returns a file list from the current directory.

For a complete list of properties and methods, please visit Microsoft's C# documentation.

The FileInfo Class

The FileInfo class is derived from the FileSystemInfo class. It has properties and instance methods for creating, copying, deleting, moving, and opening of files, and helps in the creation of FileStream objects. This class cannot be inherited.

Following are some commonly used properties of the FileInfo class −

Sr.No. Property & Description
1

Attributes

Gets the attributes for the current file.

2

CreationTime

Gets the creation time of the current file.

3

Directory

Gets an instance of the directory which the file belongs to.

4

Exists

Gets a Boolean value indicating whether the file exists.

5

Extension

Gets the string representing the file extension.

6

FullName

Gets the full path of the file.

7

LastAccessTime

Gets the time the current file was last accessed.

8

LastWriteTime

Gets the time of the last written activity of the file.

9

Length

Gets the size, in bytes, of the current file.

10

Name

Gets the name of the file.

Following are some commonly used methods of the FileInfo class −

Sr.No. Method & Description
1

public StreamWriter AppendText()

Creates a StreamWriter that appends text to the file represented by this instance of the FileInfo.

2

public FileStream Create()

Creates a file.

3

public override void Delete()

Deletes a file permanently.

4

public void MoveTo(string destFileName)

Moves a specified file to a new location, providing the option to specify a new file name.

5

public FileStream Open(FileMode mode)

Opens a file in the specified mode.

6

public FileStream Open(FileMode mode, FileAccess access)

Opens a file in the specified mode with read, write, or read/write access.

7

public FileStream Open(FileMode mode, FileAccess access, FileShare share)

Opens a file in the specified mode with read, write, or read/write access and the specified sharing option.

8

public FileStream OpenRead()

Creates a read-only FileStream

9

public FileStream OpenWrite()

Creates a write-only FileStream.

For complete list of properties and methods, please visit Microsoft's C# documentation.

Example

The following example demonstrates the use of the above-mentioned classes −

using System;
using System.IO;

namespace WindowsFileApplication {
   class Program {
      static void Main(string[] args) {
         //creating a DirectoryInfo object
         DirectoryInfo mydir = new DirectoryInfo(@"c:\Windows");
         
         // getting the files in the directory, their names and size
         FileInfo [] f = mydir.GetFiles();
         foreach (FileInfo file in f) {
            Console.WriteLine("File Name: {0} Size: {1}", file.Name, file.Length);
         }
         
         Console.ReadKey();
      }
   }
}

When you compile and run the program, it displays the names of files and their respective sizes in the Windows directory.


Chương trình học


  1. Cài đặt môi trường Lập trình C# 2
    1. Cài đặt Visual Studio #13
    2. Môi trường phát triển .NET #232
  2. Nhập môn Lập trình C# 18
    1. Giới thiệu ngôn ngữ lập trình C# #323
    2. Cấu trúc chương trình C# #166
    3. Cú pháp cơ bản C# #238
    4. Các kiểu dữ liệu trong C# #240
    5. Chuyển đổi kiểu dữ liệu trong C# #245
    6. Khởi tạo biến trong C# #247
    7. Hằng số trong C# #249
    8. Toán tử trong C# #251
    9. Điều kiện trong C# #253
    10. Vòng lặp trong C# #262
    11. Tính bao đóng trong C# #274
    12. Tạo phương thức/hàm trong C# #276
    13. Đối tượng Nullable trong C# #280
    14. Mảng trong C# #283
    15. Chuỗi trong C# #349
    16. Cấu trúc trong C# #351
    17. Enums trong C# #353
    18. Truyền Tham số Reference hay Tham trị (Value) trong C# #10172
  3. Hướng đối tượng trong C# 12
    1. Class trong C# #355
    2. Kế thừa trong C# #359
    3. Tính đa hình trong C# #361
    4. Nạp chồng toán tử trong C# #534
    5. Giao diện (Interface) trong C# #537
    6. Namespace trong C# #540
    7. Các lệnh tiền xử lý trong C# #543
    8. Biểu thức chính quy (Regular) trong C# #726
    9. Bắt các lỗi/ngoại lệ (Exception) trong C# #730
    10. Xử lý Đọc/Ghi File trong C# #732
    11. LINQ trong C# #7805
    12. Mã hóa (Encryption) và Giải mã (Decryption) trong C# #11880
  4. Các kỹ thuật nâng cao trong C# 2
    1. Thuộc tính (Attributes) trong C# #739
    2. Biên dịch ngược (Reflection) trong C# #741
  5. Bài tập thực hành 28
    1. Khai báo các Kiểu dữ liệu cho Mẫu Lý lịch A2 và Mẫu Hóa đơn Bán hàng #7703
    2. Sử dụng các Toán tử cơ bản trong C# #7704
    3. Kiểm tra số chẵn hay lẻ #171
    4. Thay đổi vị trí của 2 phần tử #175
    5. Tính tổng các kí tự số #224
    6. Đảo ngược con số #229
    7. Tạo chương trình ATM đơn giản #466
    8. Tạo chương trình ATM đơn giản với các phương án rút tiền theo các mệnh giá #477
    9. Tìm số Max, Min trong mảng 2 chiều #480
    10. Tạo cấu trúc lưu trữ thông tin Nhân viên #654
    11. Làm quen Hướng đối tượng trong C# #661
    12. Mã hóa chuỗi với Hacker Speak (H4ck3rSp34k) #681
    13. Mã hóa chuỗi với Alternating Captions (AlTeRnAtInG_CaPs​​​​​) #683
    14. Tính tổng 2 số nhỏ nhất trong danh sách #689
    15. Trích xuất thông tin từ dữ liệu trong FILE TEXT #760
    16. In bảng cửu chương #7747
    17. In tam giác Nhị phân #7749
    18. In tam giác Số ký tự #7751
    19. Đếm số 1 #7754
    20. Sử dụng Mảng 2 chiều để in tên dạng Asterisk ra màn hình #7761
    21. Sử dụng Mảng 1 chiều để phân tách Tên với khoảng cách #7765
    22. Bài tập Biểu thức Chính quy (Regular Expression) #7779
    23. Ghi log lỗi với File và Try Catch #7795
    24. Ghi Access log #7796
    25. LINQ group by tên tập tin #7812
    26. LINQ với collection #7822
    27. Tạo chương trình Quản lý Danh sách Sinh viên và Giảng viên #8554
    28. Bài tập tạo các CLASS OOP C# căn bản 1 #11842
  6. Kiểm tra kiến thức 1
    1. Kiểm tra kiến thức Lập trình C# #205
  7. Kiểm tra kiến thức - Đồ án 4
    1. Bài tập Kiểm tra Thực hành C# - Đề 01 #903
    2. Bài tập Kiểm tra Thực hành C# - Đề 02 #7825
    3. Đề thi Aptech C# - Đề 01 #11888
    4. Đề thi Aptech C# - Đề 02 #11891
Các bài học

Chương trình học

Bao gồm Module, Chương, Bài học, Bài tập, Kiểm tra...

Chương trình học


  1. Cài đặt môi trường Lập trình C# 2
    1. Cài đặt Visual Studio #13
    2. Môi trường phát triển .NET #232
  2. Nhập môn Lập trình C# 18
    1. Giới thiệu ngôn ngữ lập trình C# #323
    2. Cấu trúc chương trình C# #166
    3. Cú pháp cơ bản C# #238
    4. Các kiểu dữ liệu trong C# #240
    5. Chuyển đổi kiểu dữ liệu trong C# #245
    6. Khởi tạo biến trong C# #247
    7. Hằng số trong C# #249
    8. Toán tử trong C# #251
    9. Điều kiện trong C# #253
    10. Vòng lặp trong C# #262
    11. Tính bao đóng trong C# #274
    12. Tạo phương thức/hàm trong C# #276
    13. Đối tượng Nullable trong C# #280
    14. Mảng trong C# #283
    15. Chuỗi trong C# #349
    16. Cấu trúc trong C# #351
    17. Enums trong C# #353
    18. Truyền Tham số Reference hay Tham trị (Value) trong C# #10172
  3. Hướng đối tượng trong C# 12
    1. Class trong C# #355
    2. Kế thừa trong C# #359
    3. Tính đa hình trong C# #361
    4. Nạp chồng toán tử trong C# #534
    5. Giao diện (Interface) trong C# #537
    6. Namespace trong C# #540
    7. Các lệnh tiền xử lý trong C# #543
    8. Biểu thức chính quy (Regular) trong C# #726
    9. Bắt các lỗi/ngoại lệ (Exception) trong C# #730
    10. Xử lý Đọc/Ghi File trong C# #732
    11. LINQ trong C# #7805
    12. Mã hóa (Encryption) và Giải mã (Decryption) trong C# #11880
  4. Các kỹ thuật nâng cao trong C# 2
    1. Thuộc tính (Attributes) trong C# #739
    2. Biên dịch ngược (Reflection) trong C# #741
  5. Bài tập thực hành 28
    1. Khai báo các Kiểu dữ liệu cho Mẫu Lý lịch A2 và Mẫu Hóa đơn Bán hàng #7703
    2. Sử dụng các Toán tử cơ bản trong C# #7704
    3. Kiểm tra số chẵn hay lẻ #171
    4. Thay đổi vị trí của 2 phần tử #175
    5. Tính tổng các kí tự số #224
    6. Đảo ngược con số #229
    7. Tạo chương trình ATM đơn giản #466
    8. Tạo chương trình ATM đơn giản với các phương án rút tiền theo các mệnh giá #477
    9. Tìm số Max, Min trong mảng 2 chiều #480
    10. Tạo cấu trúc lưu trữ thông tin Nhân viên #654
    11. Làm quen Hướng đối tượng trong C# #661
    12. Mã hóa chuỗi với Hacker Speak (H4ck3rSp34k) #681
    13. Mã hóa chuỗi với Alternating Captions (AlTeRnAtInG_CaPs​​​​​) #683
    14. Tính tổng 2 số nhỏ nhất trong danh sách #689
    15. Trích xuất thông tin từ dữ liệu trong FILE TEXT #760
    16. In bảng cửu chương #7747
    17. In tam giác Nhị phân #7749
    18. In tam giác Số ký tự #7751
    19. Đếm số 1 #7754
    20. Sử dụng Mảng 2 chiều để in tên dạng Asterisk ra màn hình #7761
    21. Sử dụng Mảng 1 chiều để phân tách Tên với khoảng cách #7765
    22. Bài tập Biểu thức Chính quy (Regular Expression) #7779
    23. Ghi log lỗi với File và Try Catch #7795
    24. Ghi Access log #7796
    25. LINQ group by tên tập tin #7812
    26. LINQ với collection #7822
    27. Tạo chương trình Quản lý Danh sách Sinh viên và Giảng viên #8554
    28. Bài tập tạo các CLASS OOP C# căn bản 1 #11842
  6. Kiểm tra kiến thức 1
    1. Kiểm tra kiến thức Lập trình C# #205
  7. Kiểm tra kiến thức - Đồ án 4
    1. Bài tập Kiểm tra Thực hành C# - Đề 01 #903
    2. Bài tập Kiểm tra Thực hành C# - Đề 02 #7825
    3. Đề thi Aptech C# - Đề 01 #11888
    4. Đề thi Aptech C# - Đề 02 #11891

Bài học trước Bài học tiếp theo