Lớp và phương thức
- 22-11-2023
- Toanngo92
- 0 Comments
Buổi học này cung cấp một cái nhìn tổng quan về C# như một ngôn ngữ lập trình hướng đối tượng. Nó mô tả việc sử dụng các lớp, đối tượng và phương thức trong C#. Buổi học giới thiệu về các bộ điều chỉnh truy cập khác nhau và giải thích khái niệm của phương pháp nạp chồng, trong đó bạn có thể có nhiều phương thức cùng tên. Cuối cùng, buổi học giải thích về việc sử dụng các hàm khởi tạo và hủy bỏ.
- Giải thích về lớp và đối tượng
- Định nghĩa và mô tả về phương thức
- Liệt kê các bộ điều chỉnh truy cập
- Giải thích về phương pháp nạp chồng
- Định nghĩa và mô tả về hàm khởi tạo và hàm hủy bỏ
Mục lục
Lập trình hướng đối tượng
Ngôn ngữ lập trình luôn được thiết kế dựa trên hai khái niệm cơ bản, đó là dữ liệu và cách thức để thao tác dữ liệu. Các ngôn ngữ truyền thống như Pascal và C sử dụng phương pháp thủ tục hướng vào cách thức thao tác dữ liệu hơn là vấn đề dữ liệu chính nó. Phương pháp này có một số hạn chế như thiếu tái sử dụng và khả năng bảo trì kém.
Để vượt qua những khó khăn này, lập trình hướng đối tượng (OOP) đã được giới thiệu, tập trung vào dữ liệu chứ không phải cách thức ‘thao tác dữ liệu’. Phương pháp hướng đối tượng xác định các đối tượng như các thực thể có một bộ giá trị xác định và một bộ các hoạt động được thực hiện trên các giá trị này.
Ghi chú: Một số ngôn ngữ lập trình hướng đối tượng bao gồm C++, C#, Java và VB.NET.
Các tính năng
Lập trình hướng đối tượng cung cấp một số tính năng phân biệt nó so với phương pháp lập trình truyền thống. Những tính năng này như sau:
- Trừu tượng hóa
Trừu tượng hóa là tính năng rút ra chỉ thông tin cần thiết từ các đối tượng. Ví dụ, hãy xem một chiếc tivi là một đối tượng. Nó có một tài liệu hướng dẫn cách sử dụng. Tuy nhiên, tài liệu này không hiển thị tất cả các chi tiết kỹ thuật của chiếc tivi, chỉ đưa ra một sự trừu tượng cho người dùng.
- Đóng gói
Chi tiết về những gì một lớp chứa không cần phải được hiển thị cho các lớp và đối tượng khác sử dụng nó. Thay vào đó, chỉ có thể làm cho thông tin cụ thể có thể được hiển thị và những thông tin khác có thể được ẩn. Điều này được thực hiện thông qua đóng gói, còn được gọi là ẩn dữ liệu. Cả trừu tượng hóa và đóng gói là bổ sung cho nhau.
- Kế thừa
Kế thừa là quá trình tạo ra một lớp mới dựa trên các thuộc tính và phương thức của một lớp hiện có. Lớp hiện có được gọi là lớp cơ sở trong khi lớp mới tạo ra được gọi là lớp dẫn xuất. Đây là một khái niệm rất quan trọng trong lập trình hướng đối tượng vì nó giúp tái sử dụng các thuộc tính và phương thức được kế thừa.
- Đa hình
Đa hình là khả năng hoạt động khác nhau trong các tình huống khác nhau. Nó thường được thấy trong các chương trình có nhiều phương thức được khai báo cùng tên nhưng với các tham số khác nhau và hành vi khác nhau.
Lớp và Đối tượng
Các chương trình C# được tạo thành từ các lớp đại diện cho các thực thể của chương trình. Các chương trình cũng bao gồm mã để khởi tạo các lớp thành đối tượng. Khi chương trình chạy, các đối tượng được tạo cho các lớp và chúng có thể tương tác với nhau để cung cấp các chức năng của chương trình.
Đối tượng
Một đối tượng là một thực thể có thể chạm được như một chiếc xe, một cái bàn hoặc một chiếc cặp. Mỗi đối tượng có một số đặc điểm và có khả năng thực hiện một số hành động.
Khái niệm về đối tượng trong thế giới thực cũng có thể được mở rộng sang thế giới lập trình. Giống như phiên bản thế giới thực của nó, một đối tượng trong một ngôn ngữ lập trình có một danh tính duy nhất, trạng thái và hành vi. Danh tính của đối tượng phân biệt nó với các đối tượng khác cùng loại. Trạng thái của đối tượng liên quan đến các đặc điểm hoặc thuộc tính của nó trong khi hành vi của đối tượng bao gồm các hành động của nó. Nói một cách đơn giản, một đối tượng có các đặc điểm khác nhau có thể mô tả nó. Các đặc điểm này có thể là tên công ty, mô hình, giá, số dặm đã đi, và các thông tin khác.
Hình bên dưới hiển thị một ví dụ về một đối tượng có danh tính, trạng thái và hành vi.
Một đối tượng lưu trữ danh tính và trạng thái của nó trong các trường (còn được gọi là biến) và tiết lộ hành vi của nó thông qua các phương thức.
Các lớp
Nhiều đối tượng có một trạng thái và hành vi chung và do đó có thể được nhóm lại dưới một lớp duy nhất. Ví dụ, một Ford Mustang, một Volkswagen Beetle và một Toyota Camry có thể được nhóm lại dưới lớp Ô tô. Ở đây, Ô tô là lớp trong khi Ford Mustang, Volkswagen Beetle và Toyota Camry là các đối tượng của lớp Ô tô.
Hình bên dưới hiển thị các lớp của Ô tô
Tạo các lớp
Khái niệm về các lớp trong thế giới thực có thể được mở rộng sang thế giới lập trình, tương tự như khái niệm về các đối tượng. Trong các ngôn ngữ lập trình hướng đối tượng như C#, một lớp là một khuôn mẫu hoặc bản thiết kế xác định trạng thái và hành vi của tất cả các đối tượng thuộc lớp đó.
Một lớp bao gồm các trường, thuộc tính, phương thức, và các thành phần khác, được gọi chung là thành viên dữ liệu của lớp. Trong C#, khai báo lớp bắt đầu bằng từ khóa “class” tiếp theo là tên của lớp.
Cú pháp sau được sử dụng để khai báo một lớp:
class <ClassName>
{
//classmembers
}
trong đó,
ClassName: Xác định tên của class.
Hình bên dưới hiển thị mẫu của 1 class
Hướng dẫn đặt tên cho các Lớp
Có một số quy ước cần tuân theo khi đặt tên lớp trong quá trình tạo lớp. Những quy ước này giúp bạn tuân theo một tiêu chuẩn khi đặt tên lớp. Các quy ước này nói rằng tên của một lớp:
- Phải là một danh từ.
- Không thể viết hoa chữ cái đầu của từ và phải viết hoa chữ cái đầu của mỗi từ.
- Nên đơn giản, mô tả và có ý nghĩa.
- Không thể là một từ khóa trong C#.
- Không thể bắt đầu bằng một chữ số. Tuy nhiên, chúng có thể bắt đầu bằng ký tự ‘@”‘ hoặc một dấu gạch dưới (_), mặc dù điều này thường không phải là một thực hành được khuyến nghị.
Ví dụ về tên lớp hợp lệ: Account, @Account, và _Account. Các ví dụ về tên lớp không hợp lệ là: Zaccount, class, Acc count, và Account 123.
Phương thức Main()
Phương thức Main() cho biết với CLR (Common Language Runtime) rằng đây là phương thức đầu tiên của chương trình. Phương thức này được khai báo bên trong một lớp và chỉ ra nơi bắt đầu thực thi chương trình. Mọi chương trình C# cần được thực thi phải có một phương thức Main() vì nó là điểm vào của chương trình. Kiểu trả về của Main() trong C# có thể là int hoặc void.
Khởi tạo đối tượng
Khi bạn tạo một lớp, việc tạo một đối tượng của lớp đó là cần thiết để truy cập các biến và phương thức được định nghĩa bên trong. Trong C#, một đối tượng được khởi tạo bằng từ khóa new. Khi gặp từ khóa new, trình biên dịch JIT (Just-In-Time) cấp phát bộ nhớ cho đối tượng và trả về một tham chiếu đến bộ nhớ đã cấp phát đó.
Cú pháp sau được sử dụng để khởi tạo một đối tượng:
<ClassName> <objectName> = new <ClassName>();
trong đó,
- ClassName: Xác định tên của lớp.
- objectName: Xác định tên của đối tượng.
Hình bên dưới hiển thị một ví dụ về việc khởi tạo đối tượng.
Lưu ý: Khi mã code viết bằng một ngôn ngữ tương thích với .NET như C# được biên dịch, kết quả của mã code là MSIL (Microsoft Intermediate Language). Để chạy mã này trên máy tính, mã MSIL phải được chuyển đổi thành mã nguyên bản của hệ điều hành. Điều này được thực hiện bởi trình biên dịch JIT.
Phương thức
Phương thức là các hàm được khai báo trong một lớp và có thể được sử dụng để thực hiện các hoạt động trên các biến của lớp. Chúng là các khối mã có thể nhận tham số và có thể hoặc không cần trả về một giá trị. Một phương thức thực hiện hành vi của một đối tượng, có thể được truy cập bằng cách khởi tạo đối tượng của lớp trong đó nó được định nghĩa và sau đó gọi phương thức đó. Ví dụ, lớp Ô tô có thể có một phương thức Brake() biểu diễn hành động ‘Kích phanh’. Để thực hiện hành động này, phương thức Brake() sẽ được gọi bởi một đối tượng của lớp Ô tô.
Tạo Phương thức
Phương thức chỉ định cách thức thực hiện một thao tác cụ thể trên các thành viên dữ liệu cần thiết của lớp. Chúng được khai báo bên trong một lớp bằng cách xác định kiểu trả về của phương thức, tên phương thức và một danh sách tùy chọn các tham số. Có một số quy ước cần tuân theo khi đặt tên cho các phương thức. Những quy ước này nói rằng tên của một phương thức:
- Không thể là một từ khóa trong C#.
- Không thể chứa khoảng trắng.
- Không thể bắt đầu bằng một chữ số.
- Có thể bắt đầu bằng một chữ cái, dấu gạch dưới hoặc ký tự ‘@’.
Một số ví dụ về tên phương thức hợp lệ là: Add(), Sum_Add(), và @Add().
Các ví dụ về tên phương thức không hợp lệ bao gồm 5Add, Add Sum(), và int().
Cú pháp sau được sử dụng để tạo một phương thức:
<access_modifier> <return_type> <MethodName> ([list of parameters])
{
// Body of the method
}
trong đó,
- access_modifier: Xác định phạm vi truy cập cho phương thức. Nếu không có bộ điều chỉnh truy cập nào được chỉ định, thì theo mặc định, phương thức sẽ được xem xét là private.
- return_type: Xác định kiểu dữ liệu của giá trị được trả về bởi phương thức. Nếu phương thức không trả về gì, từ khóa void sẽ được sử dụng ở đây.
- MethodName: Xác định tên của phương thức.
- list of parameters: Xác định các đối số được truyền vào phương thức.
Đoạn mã bên dưới hiển thị định nghĩa của một phương thức có tên Add() để cộng hai số nguyên.
class Sum
{
int Add(int numOne, int numTwo)
{
int addResult = numOne + numTwo;
Console.WriteLine("Addition = " + addResult);
}
}
Trong Đoạn mã trên, phương thức Add() nhận hai tham số kiểu int và thực hiện phép cộng giữa hai giá trị đó. Cuối cùng, nó hiển thị kết quả của phép cộng.
Lưu ý: Phương thức Main() trong một lớp là bắt buộc nếu chương trình muốn được thực thi. Nếu một ứng dụng có hai hoặc nhiều hơn các lớp, một trong số chúng phải chứa một phương thức Main(), nếu không ứng dụng sẽ không thể được thực thi.
Gọi Phương thức
Bạn có thể gọi một phương thức trong một lớp bằng cách tạo một đối tượng của lớp. Để gọi một phương thức, tên đối tượng được theo sau bởi một dấu chấm (.) và tên của phương thức kèm theo dấu ngoặc đơn.
Trong C#, một phương thức luôn được gọi từ một phương thức khác. Phương thức trong đó một phương thức được gọi được gọi là phương thức gọi. Phương thức đã gọi được gọi là phương thức đã gọi. Hầu hết các phương thức được gọi từ phương thức Main(), đó là điểm vào của chương trình.
Hình bên dưới mô tả cách một lệnh gọi hoặc lời gọi phương thức được lưu trữ trong ngăn xếp trong bộ nhớ và cách một phương thức được định nghĩa.
Đoạn mã bên dưới được sử dụng để định nghĩa các phương thức Print() và Input() trong lớp Book và gọi chúng thông qua một đối tượng objBook trong phương thức Main().
class Book
{
string _bookName;
public string Print()
{
return _bookName;
}
public void Input(string bkName)
{
_bookName = bkName;
}
static void Main(string[] args)
{
Book objBook = new Book();
objBook.Input("C# - The Complete Reference");
Console.WriteLine(objBook.Print());
}
}
Trong Đoạn mã trên, phương thức Main() là phương thức gọi và phương thức Print() cùng Input() là các phương thức được gọi. Phương thức Input() nhận tên sách làm tham số và gán tên sách vào biến _bookName. Cuối cùng, phương thức Print() được gọi từ phương thức Main() và hiển thị tên sách ra màn hình console.
Kết quả:
C# - The Complete Reference
Tham số và Đối số của Phương thức
Các biến được bao gồm trong định nghĩa phương thức được gọi là tham số. Khi gọi phương thức, dữ liệu mà bạn gửi vào các tham số của phương thức được gọi là đối số.
Một phương thức có thể có không hoặc nhiều tham số, được đặt trong dấu ngoặc đơn và phân cách bởi dấu phẩy. Nếu phương thức không có tham số, điều đó được biểu thị bằng dấu ngoặc đơn trống.
Hình bên dưới mô tả một ví dụ về tham số và đối số của phương thức.
Đối số có tên và Tùy chọn
Một phương thức trong chương trình C# có thể chấp nhận nhiều đối số. Mặc định, các đối số được truyền dựa trên vị trí của các tham số trong chữ ký của phương thức. Tuy nhiên, người gọi phương thức có thể rõ ràng đặt tên cho một hoặc nhiều đối số đang được truyền vào phương thức thay vì truyền các đối số dựa trên vị trí của chúng. Đối số được truyền theo tên của nó thay vì vị trí của nó được gọi là đối số có tên.
Trong việc truyền các đối số có tên, thứ tự của các đối số được khai báo trong phương thức không quan trọng. Đối số thứ hai của phương thức có thể được truyền trước đối số đầu tiên. Ngoài ra, một đối số có tên có thể theo sau các đối số vị trí.
Các đối số có tên hữu ích vì bạn không cần phải nhớ chính xác thứ tự các tham số trong danh sách tham số của phương thức.
Đoạn mã bên dưới minh họa cách sử dụng đối số có tên.
class Student
{
void PrintName(string FirstName, string LastName)
{
Console.WriteLine("First Name={0}, Last Name={1}", FirstName, LastName);
}
static void Main(string[] args)
{
Student student = new Student();
/* Truyền đối số theo vị trí */
student.PrintName("Henry", "Parker");
/* Truyền đối số có tên */
student.PrintName(FirstName: "Henry", LastName: "Parker");
student.PrintName(LastName: "Parker", FirstName: "Henry");
/* Truyền đối số có tên sau đối số vị trí */
student.PrintName("Henry", LastName: "Parker");
}
}
Trong Đoạn mã trên, lời gọi đầu tiên đến phương thức PrintName() truyền các đối số theo vị trí. Lời gọi thứ hai và thứ ba truyền các đối số có tên theo thứ tự khác nhau. Lời gọi thứ tư truyền một đối số theo vị trí sau đó là một đối số có tên.
Kết quả:
First Name=Henry, Last Name=Parker
First Name=Henry, Last Name=Parker
First Name=Henry, Last Name=Parker
First Name=Henry, Last Name=Parker
Lưu ý: Trong một lời gọi phương thức, bạn không thể truyền một đối số có tên theo sau một đối số vị trí.
Đoạn mã bên dưới cho thấy một ví dụ khác về việc sử dụng đối số có tên.
class TestProgram
{
void Count(int boys, int girls)
{
Console.WriteLine(boys + girls);
}
static void Main(string[] args)
{
TestProgram objTest = new TestProgram();
objTest.Count(boys: 16, girls: 24);
}
}
C# cũng hỗ trợ các đối số tùy chọn trong phương thức. Một đối số tùy chọn, như tên gọi của nó, là tùy chọn và có thể bị bỏ qua bởi người gọi phương thức. Mỗi đối số tùy chọn có một giá trị mặc định được sử dụng nếu người gọi không cung cấp giá trị đối số đó.
Đoạn mã bên dưới mô tả cách sử dụng đối số tùy chọn.
class OptionalParameterExample
{
void PrintMessage(string message = "Hello user!")
{
Console.WriteLine("{0}", message);
}
static void Main(string[] args)
{
OptionalParameterExample opExample = new OptionalParameterExample();
opExample.PrintMessage("Welcome User!");
opExample.PrintMessage();
}
}
Trong Đoạn mã trên, phương thức PrintMessage() khai báo một đối số tùy chọn là message với giá trị mặc định là “Hello user!”. Lời gọi đầu tiên đến phương thức PrintMessage() truyền một giá trị đối số được in ra trên console. Lời gọi thứ hai không truyền bất kỳ giá trị nào và do đó, giá trị mặc định được in ra trên console.
Kết quả:
Welcome User!
Hello user!
Các lớp tĩnh
Các lớp không thể khởi tạo hoặc kế thừa được gọi là các lớp tĩnh. Để tạo một lớp tĩnh, trong khi khai báo lớp, từ khóa static được sử dụng trước tên lớp. Một lớp tĩnh có thể bao gồm các thành viên dữ liệu tĩnh và các phương thức tĩnh.
Không thể tạo một phiên bản của một lớp tĩnh bằng cách sử dụng từ khóa new. Các đặc điểm chính của lớp tĩnh như sau:
- Chúng chỉ có thể chứa các thành viên tĩnh.
- Chúng không thể được khởi tạo.
- Chúng không thể được kế thừa.
- Chúng không thể chứa các constructor của thể hiện. Tuy nhiên, người phát triển có thể tạo các constructor tĩnh để khởi tạo các thành viên tĩnh.
Vì không cần tạo đối tượng của lớp tĩnh để gọi các phương thức cần thiết, việc triển khai của chương trình đơn giản và nhanh hơn so với các chương trình chứa các lớp thể hiện.
Đoạn mã bên dưới tạo một lớp tĩnh Product có các biến tĩnh là _productId và _price và một phương thức tĩnh được gọi là Display(). Nó cũng định nghĩa một constructor Product() khởi tạo các biến lớp thành 10 và 156.32 tương ứng.
using System;
static class Product
{
static int _productId;
static double _price;
static Product()
{
_productId = 10;
_price = 156.32;
}
public static void Display()
{
Console.WriteLine("Product ID: " + _productId);
Console.WriteLine("Product price: " + _price);
}
}
class Medicine
{
static void Main(string[] args)
{
Product.Display();
}
}
Trong Đoạn mã trên, vì lớp Product là một lớp tĩnh, nó không được khởi tạo. Vì vậy, phương thức Display() được gọi bằng cách đề cập đến tên lớp theo sau là dấu chấm (.) và tên của phương thức.
Kết quả:
Product ID: 10
Product price: 156.32
Các phương thức tĩnh
Một phương thức, mặc định, được gọi bằng cách sử dụng một đối tượng của lớp. Tuy nhiên, có thể có một phương thức được gọi mà không cần tạo bất kỳ đối tượng của lớp nào. Điều này có thể thực hiện bằng cách khai báo một phương thức là tĩnh. Một phương thức tĩnh được khai báo bằng từ khóa static. Ví dụ, phương thức Main() là một phương thức tĩnh và nó không cần bất kỳ đối tượng của lớp nào để gọi. Một phương thức tĩnh có thể trực tiếp tham chiếu chỉ đến các biến và các phương thức tĩnh khác của lớp. Tuy nhiên, phương thức tĩnh có thể tham chiếu đến các phương thức và biến không tĩnh bằng cách sử dụng một phiên bản của lớp.
Đoạn mã bên dưới tạo một phương thức tĩnh Addition() trong lớp Calculate và sau đó, gọi nó trong một lớp khác có tên là StaticMethods.
class Calculate
{
public static void Addition(int val1, int val2)
{
Console.WriteLine(val1 + val2);
}
public void Multiply(int val1, int val2)
{
Console.WriteLine(val1 * val2);
}
}
class StaticMethods
{
static void Main(string[] args)
{
Calculate.Addition(10, 50);
Calculate objCal = new Calculate();
objCal.Multiply(10, 20);
}
}
Trong Đoạn mã trên, phương thức tĩnh Addition() được gọi bằng cách sử dụng tên của lớp trong khi phương thức Multiply() được gọi bằng cách sử dụng phiên bản của lớp. Cuối cùng, kết quả của phép cộng và phép nhân được hiển thị trên cửa sổ console.
Hình bên dưới hiển thị cách gọi một phương thức tĩnh.
Biến tĩnh
Ngoài các phương thức tĩnh, bạn cũng có thể có các biến tĩnh trong C#. Một biến tĩnh là một biến đặc biệt được truy cập mà không cần sử dụng một đối tượng của lớp. Một biến được khai báo là tĩnh bằng cách sử dụng từ khóa static. Khi một biến tĩnh được tạo, nó được khởi tạo tự động trước khi nó được truy cập.
Chỉ có một bản sao của biến tĩnh được chia sẻ bởi tất cả các đối tượng của lớp. Do đó, một thay đổi trong giá trị của một biến như vậy sẽ phản ánh qua tất cả các đối tượng của lớp. Một phiên bản của một lớp không thể truy cập các biến tĩnh.
Hình bên dưới hiển thị các biến tĩnh.
Công cụ sửa đổi quyền truy cập
Lập trình hướng đối tượng cho phép bạn hạn chế quyền truy cập của các thành viên dữ liệu được xác định trong một lớp để chỉ có các lớp cụ thể có thể truy cập chúng. Để chỉ định những hạn chế này, C# cung cấp các bộ điều chỉnh truy cập cho phép bạn xác định các lớp nào có thể truy cập các thành viên dữ liệu của một lớp cụ thể. Các bộ điều chỉnh truy cập được chỉ định bằng các từ khóa trong C#.
Trong C#, có bốn bộ điều chỉnh truy cập phổ biến. Chúng là:
- public
Bộ điều chỉnh truy cập public cung cấp cấp độ truy cập linh hoạt nhất. Các thành viên được khai báo là public có thể được truy cập ở bất kỳ đâu trong lớp cũng như từ các lớp khác. Đoạn mã bên dưới khai báo một biến string public có tên là Name để lưu trữ tên của người đó. Điều này có nghĩa là nó có thể được truy cập công khai bởi bất kỳ lớp nào khác.
class Employees
{
// Không hạn chế truy cập
public string Name = "Wilson";
}
- private
Bộ điều chỉnh truy cập private cung cấp cấp độ truy cập ít linh hoạt nhất. Các thành viên private chỉ có thể truy cập được trong lớp mà chúng được khai báo.
Đoạn mã bên dưới khai báo một biến được gọi là _salary là private, điều này có nghĩa là nó không thể được truy cập bởi bất kỳ lớp nào khác ngoại trừ lớp Employee.
class Employees
{
// Chỉ có thể truy cập trong lớp này
private float _salary;
}
- protected
Bộ điều chỉnh truy cập protected cho phép các thành viên của lớp có thể được truy cập trong lớp cũng như trong các lớp dẫn xuất.
Đoạn mã bên dưới khai báo một biến được gọi là Salary là protected, điều này có nghĩa là nó chỉ có thể được truy cập bởi lớp Employee và các lớp dẫn xuất của nó.
class Employee
{
// Truy cập bảo vệ
protected float Salary;
}
- internal
Bộ điều chỉnh truy cập internal cho phép các thành viên của lớp chỉ có thể được truy cập trong các lớp cùng assembly. Một assembly là một tập tin được tạo tự động bởi trình biên dịch sau khi biên dịch thành công một ứng dụng .NET.
Đoạn mã bên dưới khai báo một biến được gọi là NumOne là internal, điều này có nghĩa là nó chỉ có thể được truy cập ở mức assembly.
public class Sample
{
// Chỉ có thể truy cập trong cùng assembly
internal static int NumOne = 3;
}
Hình bên dưới hiển thị các cấp độ tiếp cận khác nhau.
Lưu ý: C# cũng hỗ trợ các loại cấu trúc dữ liệu khác như interfaces, enumerations và structures. Bốn cấp độ truy cập – public, private, protected và internal – cũng có thể được áp dụng cho các loại này.
Từ khóa ref và out
Từ khóa ref khiến các đối số được truyền vào một phương thức theo tham chiếu. Trong gọi theo tham chiếu, phương thức được gọi thay đổi giá trị của các tham số được truyền vào nó từ phương thức gọi. Bất kỳ thay đổi nào được thực hiện cho các tham số trong phương thức được gọi sẽ được phản ánh trong các tham số được truyền từ phương thức gọi khi kiểm soát truyền về cho phương thức gọi.
Điều quan trọng là cả phương thức được gọi và phương thức gọi phải chịu trách nhiệm chỉ định từ khóa ref trước các tham số cần thiết. Các biến được truyền theo tham chiếu từ phương thức gọi phải được khởi tạo trước tiên.
Cú pháp sau được sử dụng để truyền giá trị theo tham chiếu bằng từ khóa ref:
<access_modifier> <return_type> <MethodName>(ref parameter1, ref parameter2, parameter3, parameter4, ..., parameterN)
{
//actions to be performed
}
trong đó,
- parameter1, parameter2: Xác định rằng có thể có bất kỳ số lượng tham số nào và không nhất thiết phải là tham số ref.
Đoạn mã bên dưới sử dụng từ khóa ref để truyền các đối số theo tham chiếu.
class RefParameters
{
static void Calculate(ref int numValueOne, ref int numValueTwo)
{
numValueOne = numValueOne * 2;
numValueTwo = numValueTwo / 2;
}
static void Main(string[] args)
{
int numOne = 10;
int numTwo = 20;
Console.WriteLine("Value of Num1 and Num2 before calling method: " + numOne + ", " + numTwo);
Calculate(ref numOne, ref numTwo);
Console.WriteLine("Value of Num1 and Num2 after calling method: " + numOne + ", " + numTwo);
}
}
Trong đoạn mã trên, phương thức Calculate() được gọi từ phương thức Main(), trong đó lấy các tham số được tiền tố bằng từ khóa ref. Cùng từ khóa này cũng được sử dụng trong phương thức Calculate() trước các biến numValueOne và numValueTwo. Trong phương thức Calculate(), các phép nhân và chia được thực hiện trên các giá trị được truyền làm tham số và kết quả được lưu trữ trong các biến numValueOne và numValueTwo tương ứng. Các giá trị kết quả được lưu trong các biến này cũng được phản ánh trong các biến numOne và numTwo tương ứng vì các giá trị được truyền theo tham chiếu đến phương thức Calculate(). Hình bên dưới hiển thị việc sử dụng từ khóa ref.
Lưu ý: Không cần thiết phải có tất cả các tham số phương thức đều là tham số ref.
Từ khóa out tương tự như từ khóa ref và khiến các đối số được truyền theo tham chiếu. Sự khác biệt duy nhất giữa hai từ khóa này là từ khóa out không yêu cầu các biến được truyền theo tham chiếu phải được khởi tạo trước. Cả phương thức được gọi và phương thức gọi phải một cách rõ ràng sử dụng từ khóa out.
Dưới đây là cú pháp được sử dụng để truyền giá trị theo tham chiếu bằng từ khóa out:
<access_modifier> <return_type> <MethodName>(out parameter1, out parameter2, parameterN)
{
// các hành động được thực hiện
}
trong đó,
- parameter: Chỉ ra rằng có thể có bất kỳ số lượng tham số nào và không cần thiết phải tất cả các tham số đều là tham số out.
class OutParameters
{
static void Depreciation(out int val)
{
val = 20000;
int dep = val * 5 / 100;
int amt = val - dep;
Console.WriteLine("Depreciation Amount: " + dep);
Console.WriteLine("Reduced value after depreciation: " + amt);
}
static void Main(string[] args)
{
int value;
Depreciation(out value);
}
}
Trong đoạn mã trên, phương thức Depreciation() được gọi từ phương thức Main() truyền tham số val bằng từ khóa out. Trong phương thức Depreciation(), việc hao mòn được tính toán và số tiền hao mòn kết quả được trừ từ biến val. Giá trị cuối cùng trong biến amt được hiển thị là đầu ra.
Hình bên dưới hiển thị việc sử dụng từ khóa out.
Nạp chồng Phương thức
Hãy xem xét một khu nghỉ dưỡng có hai nhân viên cùng tên là Peter. Một người là đầu bếp trong khi người kia đảm nhận việc bảo trì. Người giao hàng biết Peter nào làm công việc gì, vì vậy nếu có giao hàng rau sạch cho một ông Peter nào đó, nó sẽ được chuyển đến đầu bếp. Tương tự, nếu có giao hàng bóng đèn và dây điện cho một ông Peter, nó sẽ được chuyển đến người bảo trì.
Mặc dù có hai người tên Peter, nhưng các mặt hàng giao hàng cung cấp thông tin rõ ràng về Peter nào cần được giao hàng.
Tương tự, trong C#, hai phương thức có thể có cùng tên vì chúng có thể được phân biệt bởi chữ ký của chúng. Tính năng này được gọi là nạp chồng phương thức.
Nạp chồng Phương thức trong C#
Trong lập trình hướng đối tượng, mỗi phương thức có một chữ ký. Điều này bao gồm số lượng tham số được truyền vào phương thức, kiểu dữ liệu của các tham số và thứ tự mà các tham số được viết. Khi khai báo một phương thức, chữ ký của phương thức được viết trong dấu ngoặc đơn kế tiếp tên phương thức.
Một lớp không được phép chứa hai phương thức cùng tên và cùng chữ ký. Tuy nhiên, có thể cho một lớp có hai phương thức cùng tên nhưng có chữ ký khác nhau. Khái niệm khai báo nhiều hơn một phương thức cùng tên nhưng có các chữ ký khác nhau được gọi là nạp chồng phương thức.
Hình bên dưới hiển thị khái niệm nạp chồng phương thức bằng một ví dụ.
Đoạn mã bên dưới nạp chồng phương thức Square() để tính bình phương của các giá trị được cung cấp.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
class MethodOverloadExample
{
static void Main(string[] args)
{
Console.WriteLine("Square of integer value" + Square(5));
Console.WriteLine("Square of float value" + Square(2.5F));
}
static int Square(int num)
{
return num * num;
}
static float Square(float num)
{
return num * num;
}
}
Trong đoạn mã trên, hai phương thức có cùng tên nhưng với các tham số khác nhau được khai báo trong lớp. Hai phương thức Square() nhận tham số tương ứng là kiểu int và kiểu float. Trong phương thức Main(), tùy thuộc vào loại giá trị được truyền, phương thức thích hợp sẽ được gọi và bình phương của số cụ thể được hiển thị trong cửa sổ console.
Lưu ý: Dấu hiệu của hai phương thức được coi là giống nhau nếu đáp ứng ba điều kiện – số lượng tham số truyền vào phương thức, các loại tham số và thứ tự tham số được viết – là giống nhau. Kiểu trả về của một phương thức không phải là một phần của dấu hiệu của nó. Trong ngữ cảnh của code C#, điều này có nghĩa là khi hai phương thức có cùng số lượng tham số, các loại tham số và thứ tự của chúng trùng khớp, thì chúng được xem là có cùng dấu hiệu (signature) dù kiểu trả về của chúng có khác nhau.
Nguyên tắc và hạn chế
Khi nạp chồng phương thức trong một chương trình, bạn phải tuân theo một số hướng dẫn để đảm bảo rằng các phương thức nạp chồng hoạt động chính xác. Các hướng dẫn này như sau:
- Các phương thức cần được nạp chồng phải thực hiện cùng một nhiệm vụ. Mặc dù chương trình sẽ không tạo ra bất kỳ lỗi biên dịch nào nếu vi phạm điều này, nhưng với quy tắc tốt nhất, nên thực hiện cùng một nhiệm vụ.
- Các chữ ký (signatures) của các phương thức nạp chồng phải là duy nhất.
- Khi nạp chồng các phương thức, kiểu trả về có thể giống nhau vì nó không phải là một phần của chữ ký (signature).
- Các tham số ref và out có thể được bao gồm trong chữ ký của các phương thức nạp chồng.
Từ khóa “this”
Từ khóa this được sử dụng để tham chiếu đến đối tượng hiện tại của lớp. Nó được sử dụng để giải quyết xung đột giữa các biến có cùng tên và để truyền đối tượng hiện tại như một tham số. Bạn không thể sử dụng từ khóa this với các biến và phương thức tĩnh.
Từ khóa this được sử dụng để tham chiếu đến đối tượng hiện tại của lớp. Nó được sử dụng để giải quyết xung đột giữa các biến có cùng tên và để truyền đối tượng hiện tại như một tham số. Bạn không thể sử dụng từ khóa this với các biến và phương thức tĩnh.
Đoạn mã bên dưới sử dụng từ khóa this để tham chiếu đến các trường _length và _breadth của thể hiện hiện tại của lớp Dimension.
class Dimension {
double _length;
double _breadth;
public double Area(double length, double breadth) {
this._length = length;
this._breadth = breadth;
return _length * _breadth;
}
static void Main(string[] args) {
Dimension objDimension = new Dimension();
Console.WriteLine("Area of rectangle: " + objDimension.Area(10.5, 12.5));
}
}
Trong Đoạn mã trên, phương thức Area() có hai tham số là _length và _breadth như là các biến thành viên (instance variables). Các giá trị của các biến này được gán vào các biến lớp bằng cách sử dụng từ khóa this. Phương thức được gọi trong phương thức Main(). Cuối cùng, diện tích được tính toán và hiển thị ra ngoài cửa sổ console.
Hình bên dưới mô tả việc sử dụng từ khóa this.
Hàm tạo và hàm huỷ
Một lớp C# có thể chứa một hoặc nhiều hàm thành viên đặc biệt có cùng tên với lớp, gọi là constructor. Constructor được thực thi khi một đối tượng của lớp được tạo ra để khởi tạo đối tượng với dữ liệu. Một lớp C# cũng có thể có một destructor (chỉ cho phép một destructor cho mỗi lớp), đó là một phương thức đặc biệt cũng có cùng tên với lớp nhưng được tiền đề bằng ký tự đặc biệt ~. Destructor của một đối tượng được thực thi khi đối tượng không còn cần thiết nữa để giải phóng bộ nhớ của đối tượng.
Constructors
Trong code C#, một lớp có thể chứa nhiều biến mà việc khai báo và khởi tạo trở nên khó theo dõi nếu chúng được thực hiện trong các khối khác nhau. Tương tự, có thể có các hoạt động khởi động khác cần thực hiện trong một ứng dụng như mở một tệp tin và các hoạt động tương tự. Để đơn giản hóa các công việc này, ta sử dụng constructor. Constructor là một phương thức có cùng tên với tên của lớp. Constructors có thể khởi tạo các biến của lớp hoặc thực hiện các hoạt động khởi động chỉ một lần khi đối tượng của lớp được khởi tạo. Chúng được tự động thực thi mỗi khi một thể hiện của một lớp được tạo ra.
Hình bên dưới thể hiện khai báo của constructor.
Việc chỉ định mức độ truy cập của các constructor trong một ứng dụng là hoàn toàn có thể. Điều này được thực hiện thông qua việc sử dụng các từ khóa truy cập như sau:
- public: Xác định rằng constructor sẽ được gọi mỗi khi một lớp được khởi tạo. Việc khởi tạo có thể thực hiện từ bất kỳ đâu và từ bất kỳ assembly nào.
- private: Xác định rằng constructor này không thể được gọi bởi một thể hiện của lớp.
- protected: Xác định rằng lớp cơ sở sẽ tự khởi tạo khi các lớp dẫn xuất của nó được tạo ra. Tại đây, đối tượng lớp chỉ có thể được tạo trong các lớp dẫn xuất.
- internal: Xác định rằng constructor chỉ có thể truy cập được trong assembly hiện tại. Nó không thể được truy cập từ bên ngoài assembly.
Đoạn mã bên dưới tạo một lớp Circle với một constructor private.
public class Circle
{
private Circle() {}
}
class CircleDetails
{
public static void Main(string[] args)
{
Circle objCircle = new Circle();
}
}
Trong đoạn mã trên, chương trình sẽ tạo ra một lỗi ở thời điểm biên dịch vì một thể hiện của lớp Circle cố gắng gọi constructor được khai báo là private. Đây là một cố gắng không hợp lệ. Constructors private thường được sử dụng để ngăn chặn việc khởi tạo lớp. Nếu một lớp chỉ định nghĩa các constructor private, từ khóa new sẽ không thể được sử dụng để khởi tạo đối tượng của lớp. Điều này có nghĩa là không có lớp khác nào có thể sử dụng các thành viên dữ liệu của lớp chỉ có các constructor private. Do đó, các constructor private chỉ được sử dụng nếu một lớp chứa chỉ các thành viên dữ liệu tĩnh. Điều này là do các thành viên tĩnh được gọi thông qua tên lớp. Đoạn mã tiếp theo bên dưới được sử dụng để khởi tạo giá trị của _empName, _empAge và _deptName với sự trợ giúp của một constructor.
class Employees
{
string _empName;
int _empAge;
string _deptName;
Employees(string name, int num)
{
_empName = name;
_empAge = num;
_deptName = "Research & Development";
}
public static void Main(string[] args)
{
Employees objEmp = new Employees("John", 10);
Console.WriteLine(objEmp._deptName);
}
}
Trong Đoạn mã bên trên, một hàm khởi tạo được tạo cho lớp Employees. Khi lớp được khởi tạo, hàm khởi tạo này được gọi với các tham số là “John” và 10. Những giá trị này được lưu trữ trong các biến lớp _empName và _empAge tương ứng. Cuối cùng, bộ phận của nhân viên được hiển thị trên cửa sổ console.
Lưu ý: Hàm khởi tạo không có kiểu trả về. Điều này là do kiểu trả về ngầm định của một hàm khởi tạo là lớp đó. Cũng có thể có nhiều hàm khởi tạo quá tải trong C#.
Hàm khởi tạo mặc định
Trong C#, nếu không có hàm khởi tạo được chỉ định trong lớp, C# sẽ tạo một hàm khởi tạo mặc định cho lớp đó. Hàm khởi tạo mặc định này tự động khởi tạo tất cả các biến số kiểu số của lớp đó về 0. Nếu bạn định nghĩa một hàm khởi tạo trong lớp, hàm khởi tạo mặc định sẽ không còn được sử dụng.
Hàm khởi tạo tĩnh
Hàm khởi tạo tĩnh được sử dụng để khởi tạo các biến tĩnh của lớp và thực hiện một hành động cụ thể chỉ một lần. Nó được gọi trước khi bất kỳ thành viên tĩnh nào của lớp được truy cập. Bạn chỉ có thể có một hàm khởi tạo tĩnh trong lớp. Từ khóa static được sử dụng để khai báo một hàm khởi tạo là tĩnh. Hàm khởi tạo tĩnh không có tham số và không sử dụng bất kỳ từ khóa truy cập nào vì nó được gọi trực tiếp bởi CLR thay vì bởi đối tượng. Ngoài ra, nó không thể truy cập bất kỳ thành viên dữ liệu không tĩnh nào của lớp.
Đoạn mã bên dưới đây thể hiện cách tạo và gọi hàm khởi tạo tĩnh.
class Multiplication
{
static int _valueOne = 10;
static int _product;
static Multiplication()
{
Console.WriteLine("Static Constructor initialized");
_product = _valueOne * _valueOne;
}
public static void Method()
{
Console.WriteLine("Value of product = " + _product);
}
static void Main(string[] args)
{
Multiplication.Method();
}
}
Trong Đoạn mã trên, hàm khởi tạo tĩnh Multiplication() được sử dụng để khởi tạo biến tĩnh _product. Ở đây, hàm khởi tạo tĩnh được gọi trước khi phương thức tĩnh Method() được gọi từ phương thức Main().
Kết quả:
Static Constructor initialized
Value of product = 100
Nạp chồng Constructor
Ý nghĩa của việc khai báo nhiều hơn một constructor trong một lớp được gọi là nạp chồng (constructor overloading). Quá trình nạp chồng các constructor tương tự như việc nạp chồng các phương thức. Mỗi constructor có một chữ ký (signature) tương tự như của một phương thức. Bạn có thể khai báo nhiều constructor trong một lớp với mỗi constructor có chữ ký khác nhau. Nạp chồng constructor được sử dụng khi các đối tượng khác nhau của lớp có thể muốn sử dụng các giá trị khởi tạo khác nhau. Các constructor nạp chồng giảm công việc gán các giá trị khác nhau cho các biến thành viên mỗi khi được yêu cầu bởi các đối tượng khác nhau của lớp.
Đoạn mã bên dưới đây thể hiện việc sử dụng nạp chồng Constructor.
public class Rectangle
{
double _length;
double _breadth;
public Rectangle()
{
_length = 13.5;
_breadth = 20.5;
}
public Rectangle(double len, double wide)
{
_length = len;
_breadth = wide;
}
public double Area()
{
return _length * _breadth;
}
public static void Main(string[] args)
{
Rectangle objRect1 = new Rectangle();
Console.WriteLine("Area of recrtangle = " + objRect1.Area());
Rectangle objRect2 = new Rectangle(2.5, 6.9);
Console.WriteLine("Area of recrtangle = " + objRect2.Area());
}
}
Trong Đoạn mã trên, hai hàm khởi tạo được tạo có cùng tên, Rectangle. Tuy nhiên, chữ ký của các hàm khởi tạo này là khác nhau. Do đó, khi gọi phương thức Area() từ phương thức Main(), các tham số được truyền cho phương thức gọi được xác định. Sau đó, hàm khởi tạo tương ứng được sử dụng để khởi tạo các biến _length và _breadth. Cuối cùng, phép nhân được thực hiện trên các biến này và các giá trị diện tích được hiển thị như là kết quả.
Kết quả:
Area of recrtangle1 = 276.7
Area of recrtangle2 = 17.25
Destructors
Destructor là một phương thức đặc biệt có cùng tên với tên của lớp nhưng bắt đầu bằng ký tự ~ trước tên lớp. Destructors giải phóng ngay lập tức bộ nhớ của các đối tượng không còn được sử dụng nữa. Chúng được gọi tự động khi các đối tượng không còn được sử dụng. Bạn chỉ có thể định nghĩa một destructor trong một lớp. Ngoài ra, destructor còn có một số đặc điểm khác. Các đặc điểm này như sau:
- Destructor không thể được nạp chồng hoặc kế thừa.
- Không thể gọi destructor một cách tường minh.
- Destructor không thể chỉ định các bộ điều khiển truy cập và không thể chứa tham số.
Đoạn mã bên dưới thể hiện việc sử dụng Destructor.
class Employee
{
private int _empId;
private string _empName;
private int _age;
private double _salary;
Employee(int id, string name, int age, double sal)
{
Console.WriteLine("Constructor for Employee called");
_empId = id;
_empName = name;
_age = age;
_salary = sal;
}
~Employee()
{
Console.WriteLine("Destructor for Employee called");
}
public static void Main(string[] args)
{
Employee objEmp = new Employee(1, "John", 45, 35000);
Console.WriteLine("Employee ID: " + objEmp._empId);
Console.WriteLine("Employee Name: " + objEmp._empName);
Console.WriteLine("Age: " + objEmp._age);
Console.WriteLine("Salary: " + objEmp._salary);
}
}
Trong đoạn mã bên trên, destructor ~Employee được tạo có cùng tên với tên của lớp và constructor. Destructor sẽ tự động được gọi khi đối tượng objEmp không còn cần thiết nữa để sử dụng. Tuy nhiên, không thể xác định chính xác khi điều này sẽ xảy ra, do đó, bạn không có kiểm soát về việc destructor sẽ được thực thi vào thời điểm nào.