Trong bài này chúng ta sẽ tìm hiểu:
- Cách tạo và gọi phương thức
- Chuyển thông tin đến một phương thức
- Trả về thông tin từ một phương thức
- Định nghĩa phạm vi cục bộ và phạm vi lớp
- Vào và ra phương thức với debugger
Tạo phương thức
Phương thức là một khối các lệnh nhằm thực hiện một chức năng nào đó. Khái niệm phương thức (method) tương đương với khái niệm hàm (function) hay thủ tục (procedure) trong C, function hay subroutine trong Visual Basic. Phương thức gồm các thành phần như tên phương thức, kiểu dữ liệu trả về, danh sách các tham số, khối lệnh thực thi.
Khai báo một phương thức
Phương thức có thể được khai báo theo cú pháp sau:
Kiểu_dữ_liệu_trả_về Tên_phương_thức (Danh sách tham số)
{
// khối lệnh thực thi
}
Một lưu ý cho những người đã quen thuộc với lập trình C, C++, hay VB là các phương thức trong C# phải được khai báo trong một lớp cụ thể, không có phương thức toàn cục (global). Ví dụ phương thức Sum dùng để tính tổng hai số nguyên như sau:
int Sum (int a, int b) { return a + b; }
Trong VB, có hai từ khoá là Function và Sub cho phương thức có giá trị trả về và không có giá trị trả về. Trong C#, không dùng các từ khoá khác nhau, chỉ dùng từ khoá void cho kiểu dữ liệu trả về. Ví dụ:
void Sum (int a, int b) { int c = a + b; return; }
Trả về dữ liệu từ một phương thức
Khi một phương thức muốn trả về một giá trị, chúng ta cần dùng từ khoá return như trong ví dụ phương thức Sum đầu tiên. Nếu phương thức không có giá trị trả về, chúng ta dùng từ khoá void cho kiểu dữ liệu trả về như ví dụ hàm Sum thứ hai ở trên, từ khoá return có thể được dùng (nhưng không có giá trị) hay không cần dùng return.
Gọi phương thức
Khi gọi phương thức, chúng ta phải cung cấp đầy đủ thông tin cho phương thức như các đối số (argument) hay biến để nhận giá trị trả về (nếu có). Cú pháp để gọi một phương thức có thể như sau:
methodName là tên phương thức và lưu ý rằng C# là case-sensitive
argumentList là danh sách đối số hay danh sách các giá trị thực của các tham số khi khai báo phương thức
result là biến nhận giá trị trả về nếu có của phương thức, nếu phương thức không có kiểu dữ liệu trả về, chúng ta phải bỏ lệnh result =.
Ví dụ hàm Sum tính tổng hai số nguyên và trả về tổng được khai báo như sau:
int Sum (int a, int b) { return a + b; }
Phương thức Sum có hai tham số a và b và trả về tổng (kiểu int) a và b. Khi gọi phương thức Sum, chúng ta phải cung cấp đủ hai giá trị là các số nguyên và lưu ý Sum có giá trị trả về. Gọi Sum có thể như sau:
int S; // 3, 5 là hai đối số cho hai tham số tương ứng là a và b // giá trị trả về của Sum được lưu trữ trong biến S S = Sum (3, 5);
Các đối số có thể là các biến, cách gọi Sum ở trên tương đương:
int S; int x = 3; int y = 5; // x, y là hai đối số cho hai tham số tương ứng là a và b // giá trị trả về của Sum được lưu trữ trong biến S S = Sum (x, y);
Số đối số phải tương đương với số tham số, gọi Sum trong các trường hợp sau sẽ bị lỗi:
Sum (3); // lỗi Sum (3,5,7); //lỗi
Phạm vi (scope)
Phạm vi biến (hay phương thức) dùng để chỉ vị trí của biến (hay phương thức) trong chương trình mà tại đó biến (hay phương thức) có thể được sử dụng. Trong phần này chỉ đề cập đến phạm vi biến.
Phạm vi cục bộ (Local scope)
Biến được khai báo trong một phương thức chỉ có thể được sử dụng bởi các lệnh trong phương thức đó, các lệnh ngoài phương thức không thể sử dụng biến này. Biến trong trường hợp này có phạm vi cục bộ. Ví dụ:
class Example { void firstMethod() { int myVar; ... }// kết thúc phạm vi của biến myVar void anotherMethod() { myVar = 42; // lỗi – myVar chưa được khai báo ... } }
Phạm vi lớp (class scope)
Một biến được khai báo trong một lớp (và không thuộc một phương thức nào của lớp) có thể được truy cập bởi các phương thức trong cùng một lớp. Biến lúc này có phạm vi lớp. Ví dụ:
class Example { void firstMethod() { myVar = 42; // myVar là biến lớp ... } void anotherMethod() { myVar++; // myVar là biến lớp ... } int myVar; }
Các biến có phạm vi lớp được gọi là các field. Các field có thể khai báo trước hoặc sau các phương thức – như ví dụ trên, biến myVar được khai báo sau hai phương thức.
Quá tải phương thức (overloading methods)
Quá tải dùng để chỉ việc khai báo trùng tên (biến, phương thức, field) tại cùng một phạm vi. Quá tải có thể có một vài rủi ro như khai báo hai biến cùng tên trong cùng một phương thức, hai field cùng tên trong cùng một class, hai phương thức cùng tên trong cùng một lớp có thể dẫn đến lỗi.
Quá tải phương thức dùng để chỉ việc khai báo hai phương thức trùng tên nhưng khác nhau (số lượng, kiểu dữ liệu) về danh sách tham số trong cùng một lớp. Ví dụ phương thức Sum có các phiên bản sau:
int Sum (int a, int b) int Sum (int a, int b, int result)
Viết phương thức và debug chương trình trong Visual Studio
Trong phần này chúng ta sẽ viết chương trình tính tổng hai số nguyên bằng cách khai báo phương thức Sum (dùng Console Application) và khám phá chức năng debugger trong Visual Studio.
Viết chương trình tính tổng hai số nguyên
Viết hai phiên bản phương thức Sum (quá tải):
// phương thức tính tổng hai số nguyên và trả về tổng int Sum(int a, int b) { return a + b; } // phiên bản khác của Sum (quá tải) int Sum(int a, int b, int c) { c = a + b; return c; }
Gọi Sum trong phương thức Main, chú ý rằng chúng ta có hai phiên bản Sum:
Để gọi Sum chúng ta phải tạo một instance cho lớp Program (có thể gọi trực tiếp bằng tên lớp Program bằng cách dùng phương thức tĩnh sẽ tìm hiểu các phần sau):
int Result; Result = (new Program()).Sum(3, 5);
Đoạn mã trên tương đương với:
int Result; Program pr = new Program(); Result= pr.Sum(3, 5);
Công cụ debugger
Chúng ta có thể khám phá các bước thực thi trong các phương thức bằng công cụ debugger trong Visual Studio. Cần đảm bảo công cụ debugger hiển thị trong thanh công cụ của Visual Studio bằng cách vào menu View chọn Toolbars chọn Debug. Thanh công cụ debug như hình sau:
- Step Into: thực hiện từng lệnh trong phương thức (cả phương thức khác được gọi)
- Step Over: giống Step Into, khác biệt chỉ xảy ra nếu lệnh hiện tại gọi một phương thức khác, Step Over sẽ vẫn xem lệnh này như một lệnh bình thường và chuyển qua lệnh kế tiếp, Step Into sẽ nhảy đến phương thức được gọi và đi qua tuần tự các lệnh trong phương thức này và sẽ trở lại phương thức ban đầu ngay khi kết thúc phương thức được gọi.
- Step Out: thực hiện tất cả các lệnh kế tiếp mà không dừng lại.
Công cụ debugger rất hữu ích trong quá trình phát hiện và xử lý lỗi.
Thực hành với debugger
Đưa con trỏ chuột vào lệnh đầu tiên trong phương thức Main. Nhấp chuột phải chọn Run To Cursor sẽ xuất hiện mũi tên màu vàng trước lệnh kế tiếp và lệnh này cũng được tô vàng:
Nhấn Step Into trên thanh công cụ:
Lệnh kế tiếp sẽ được tô vàng. Nhấn Step Into lần nữa con trỏ chuột sẽ nhảy đến đầu phương thức Sum (hai tham số). Nếu chúng ta tiếp tục nhấn Step Into, các lệnh trong phương thức Sum sẽ được thực hiện tuần tự cho đến khi kết thúc con trỏ sẽ trở lại vị trí lệnh thực hiện nhảy. Nếu chúng ta muốn thoát nhanh khỏi phương thức Sum, có thể nhấn Step Out để trở về lại lệnh thực hiện nhảy.
Nhấn Step Over để thực hiện các lệnh kế, nếu muốn kết thúc nhanh có thể dùng Step Out.
Các tham số tuỳ chọn (optional parameters) và đặt tên cho các đối số (arguments)
Chúng ta có thể dùng quá tải để tạo ra các phiên bản khác nhau của một phương thức, tuy nhiên, có một vài trường hợp sẽ dẫn đến phát sinh lỗi bởi trình biên dịch (compiler). Ví dụ chúng ta có hai phiên bản của phương thức Display_Data như sau:
public void Display_Data (int intData) { ... } public void Display_Data(int moreIntData) { ... }
Gọi hai phương thức trên:
int x = 3; int y = 8; Display_Data (x); Display_Data (y);
Lúc này trình biên dịch sẽ không biết phải gọi phiên bản Display_Data nào.
Dùng quá tải có thể mang nhiều lợi ích nhưng cũng chứa nhiều rủi ro. Trong một số trường hợp chúng ta có thể dùng tham số tuỳ chọn và đặt tên đối số.
Tham số tuỳ chọn
Giả sử chúng ta có phương thức Sum tính tổng hai số nguyên và sau đó chúng ta muốn phương thức Sum có thể tính tổng ba số nguyên. Cách thứ nhất là dùng quá tải tạo hai phiên bản Sum, cách thứ hai là dùng tham số tuỳ chọn.
Một tham số là tuỳ chọn (có thể có hoặc không) nếu khi định nghĩa phương thức chúng ta truyền cho nó một giá trị khởi tạo bằng toán tử =. Ví dụ phương thức Sum sau có thể không có tham số nào hoặc có tối đa ba tham số:
int Sum (int a = 0, int b = 0, int c = 0) { return a + b + c; }
Gọi
Sum(); // kết quả là 0 Sum(2); // kết quả là 2 Sum(1,2); // kết quả là 3 Sum(1,2,3); // kết quả là 6
Nếu chúng ta muốn phương thức Sum có ít nhất một tham số thì một trong 3 tham số sẽ không được khởi tạo và bắt buộc phải là tham số đầu tiên trong danh sách tham số:
int Sum (int a, int b = 0, int c = 0) { return a + b + c; }
Gọi
Sum(); // lỗi Sum(2); // kết quả là 2 Sum(1,2); // kết quả là 3 Sum(1,2,3); // kết quả là 6
Như vậy các tham số tuỳ chọn luôn luôn đứng sau các tham số bắt buộc.
Gọi phương thức bằng tên tham số
Khi gọi một phương thức, đặc biệt là các phương thức có danh sách tham số gồm những tham số có kiểu dữ liệu khác nhau, chúng ta phải ghi nhớ thứ tự các tham số. Đây là một điều khó khăn, nhất là khi gặp danh sách tham số dài. Một cách giải quyết là cung cấp tên tham số khi gọi phương thức. Với cách gọi dùng tên tham số, chúng ta sẽ không cần quan tâm đến thứ tự tham số.
Ví dụ phương thức Student_Info được khai báo:
void Student_Info (string Name, int Age, bool Sex) { System.Console.WriteLine("Name: " + Name + "Age: " + Age + "Sex: " + Sex); }
Chúng ta có thể gọi Student_Info theo hai cách:
Student_Info("Minh", 32,true);
hay
Student_Info(Name:"Minh", Age:32, Sex:true);
Lưu ý:
– Khi gọi phương thức bằng cách gọi tên tham số mỗi đối số là một cặp: Tên tham số: giá trị tham số, ví dụ: Name:”Minh”
– Khi gọi phương thức bằng tên tham số chúng ta không cần quan tâm thứ tự tham số, ví dụ các cách gọi sau là tương đương:
Student_Info(Age:32, Name:"Minh", Sex:true); Student_Info(Name:"Minh", Age:32, Sex:true); Student_Info(Name:"Minh", Sex:true, Age:32);