TOC

This article is currently in the process of being translated into Vietnamese (~90% done).

Classes:

Method parameters

Trong bài trước chúng ta đã nói về phương thức và chúng ta sẽ xem xét khái niệm của tham số trong phương thức/hàm. Trong bai này, chúng ta sẽ đào sâu hơn vào vấn đề này. Như đã đề cập, phương thức có thể được định nghĩa mà không cần tham số, nhưng chúng thường có một hay nhiều tham số, cho phép phương thức hoàn thành chức năng của nó.

Chúng ta thấy một ví dụ đơn giản về tham số trong bài trước: phương thức AddNumber() có hai số là tham số và trả về tổng của hai số:

public int AddNumbers(int number1, int number2)
{
	return number1 + number2;
}

Chúng ta làm quen với khái niệm đóng gói chức năng trong phương thức, nhưng có thể ảnh hưởng tới kết quả khi gọi phương thức thông qua tham số:

AddNumbers(2, 3);

Result: 5

AddNumbers(38, 4);

Result: 42

Đây là kiểu cơ bản nhất của tham số nhưng hãy nói thêm về các dạng khác của tham số;

Please notice that in this article, we'll really dig into all the various types of parameters and how they can help you, but if you're just getting started with C# and you just want to see some results, the following might be a bit too complex and technical for now. Feel free to skip the rest of the article and return later when you're ready.

Optional parameters

Mặc định là khi gọi phương thức với một hay nhiều tham số thì bạn phải truyền dữ liệu cho tất cả tham số. Tuy nhiên, trong một vài tình huống, bạn có thể cần một hay vài tham số là tùy chọn. Trong một vài ngôn ngữ lập trình thì bạn có thể làm điều đó bằng cách khai báo tham số là tùy chọn, nhưng trong C#, một tham số là tùy chọn khi gán cho nó giá trị mặc định khi khai báo phương thức. Đây là một giải pháp tốt vì nó cho phép bạn viết ít code hơn.

Đây là một ví dụ về phương thức với tham số tùy chọn:

public int AddNumbers(int number1, int number2, int number3 = 0)
{
	return number1 + number2 + number3;
}

The last parameter (number3) is now optional, because we have provided a default value for it (0). When calling it, you can now supply two or three values, like this:

public void Main(string[] args)
{
	AddNumbers(38, 4);
	AddNumbers(36, 4, 2);
}

Bạn có thể có nhiều hơn một tham số tùy biến - thực tế, phương thức của bạn có thể chứa chỉ một tham số tùy biến nếu cần. Chỉ nhớ rằng tham số tùy biến phải ở phần cuối cùng trong khai báo phương thức - không được ở đầu hay giữa.

The params modifier

Một cách khác là bạn có thể dùng từ khóa params để khai báo số lượng tham số bất kỳ. Nó trông như sau:

public void GreetPersons(params string[] names) { }

Khi gọi hàm sẽ như sau:

GreetPersons("John", "Jane", "Tarzan");

Một trong những ưu điểm khi dùng params là bạn được phép không truyền tham số nào tới phương thức. Phương thức với params có thể lấy tham số thông thường cũng như tham số với params. Bên cạnh đó, chỉ một tham số dùng từ khóa params có thể dùng trong mơi phương thức. Đây là ví dụ hoàn chỉnh mà chúng ta dùng params để hiển thị tên trong phương thức GreetPersons():

public void Main(string[] args)
{
	GreetPersons("John", "Jane", "Tarzan");
}

public void GreetPersons(params string[] names)
{
	foreach(string name in names)
		Console.WriteLine("Hello " + name);
}

Parameter types: value or reference

C# và các ngôn ngữ lập trình khác, có hai loại tham số "tham biến""tham trị". Mặc định trong C# là "tham trị", có nghĩa rằng khi bạn truyền một biến tới lời gọi hàm thì cũng là gửi một bản sao của đối tượng, thay vì trỏ tới biến. Điều này có nghĩa rằng bạn có thể thay đổi tham số bên ngoài phương thức mà không ảnh hưởng tới đối tượng gốc mà bạn truyền vào dạng tham số.

Chúng ta có thể minh họa với ví dụ sau:

public void Main(string[] args)
{
	int number = 20;
	AddFive(number);
	Console.WriteLine(number);
}

public void AddFive(int number)
{
	number = number + 5;
}

Chúng ta có phương thức cơ bản gọi là AddFive() sẽ cộng 5 vào số mà chúng ta truyền vào. Vì vậy trong hàm Main(), chúng ta tạo ra một biến với giá trị 20 và sau đó gọi hàm AddFive(). Bây giờ trong dòng tiếp theo, khi chúng ta viết ra biến thì giá trị không là 25 mà là 20. Tại sao? Bởi vì mặc định thì tham số truyền vào là bản sao của đối tượng nên hàm AddFive() làm việc với bản sao này và vì vậy không bao giờ ảnh hưởng tới biến gốc.

Có hai cách để thay đổi là phương thức AddFive() cho phép biến đổi giá trị gốc: Chúng ta có thể dùng ref hay in/out.

The ref modifier

Từ khóa ref là viết tắt của "reference" và nó thay đổi tham số mặc định từ "tham biến" sang "tham trị", có nghĩa rằng chúng ta truyền địa chỉ thay vì bản sao của giá trị. Ở đây là ví dụ:

public void Main(string[] args)
{
	int number = 20;
	AddFive(ref number);
	Console.WriteLine(number);
}

public void AddFive(ref int number)
{
	number = number + 5;
}

Bạn chú ý rằng tôi đưa từ khóa "ref" vào trong hai nơi: trong khai báo phương thức và khi truyền giá trị vào tham số trong lời gọi hàm. Với sự thay đổi này, chúng ta có kết quả 25 thay vì 20, bởi vì từ khóa ref cho phép phương thức làm việc với giá trị thực sự thay vì bản sao của nó.

The out modifier

Giống như ref, từ khóa out đảm bảo rằng tham số được truyền là tham trị thay vì tham biến, nhưng khác biệt ở chỗ: Khi dùng ref thì bạn truyền giá trị ban đầu mà bạn có thể thay đổi bên trong phương thức hay để nguyên nó như vậy. Hay nói cách khác, khi dùng out, bạn ép việc khởi tạo tham số bên ngoài phương thức. Nó cũng có nghĩa rằng bạn có thể truyền vào giá trị mới khi dùng out - trình biên dịch sẽ báo lỗi khi bạn cố gắng làm việc với tham số out mà không gán giá trị cho nó.

Trong C#, một phương thức chỉ trả về một giá trị nhưng nếu bạn dùng out thì bạn có thể phá vỡ điều này bằng cách truyền nhiều tham số với out - khi phương thức được gọi thì tham số out sẽ được gán. Đây là ví dụ, chúng ta truyền hai số và sau đó dùng out để trả về cả giá trị cộng và giá trị trừ dùng những số này:

public void Main(string[] args)
{
	int addedValue, subtractedValue;
	DoMath(10, 5, out addedValue, out subtractedValue);
	Console.WriteLine(addedValue);
	Console.WriteLine(subtractedValue);
}

public void DoMath(int number1, int number2, out int addedValue, out int subtractedValue)
{
	addedValue = number1 + number2;
	subtractedValue = number1 - number2;
}
Output:

15
5

Bạn có thể thấy ở đầu ví dụ, tôi khai báo hai biến (addedValuesubstractedValue) dạng tham số out. Ở các phiên bản trước của C# thì chúng ta cần khai báo nhưng trong phiên bản 6 thì chúng ta chỉ cần khai báo chúng trực tiếp trong lời gọi hàm:

DoMath(10, 5, out int addedValue, out int subtractedValue);
Console.WriteLine(addedValue);
Console.WriteLine(subtractedValue);

The in modifier

Cũng giống out, thì in đảm bảo rằng tham số được truyền là địa chỉ thay vì bản sao của giá trị nhưng không giống out, in sẽ không cho phép thay đổi biến trong phương thức.

Vậy ý nghĩ đầu tiên của bạn có thể là: nếu tôi không thể thay đổi giá trị của tham số thì tôi có thể chỉ truyền nó như là tham số thông thường không, khi đó sự thay đổi không ảnh hưởng tới biến ban đầu. Bạn đúng, kết quả sẽ hiển thị chính xác như lúc đầu nhưng vẫn có lí do dể dùng in: Bằng cách truyền nó như là tham trị thay vì tham biến thì bạn sẽ tiết kiệm được tài nguyên vì framework không mất thời gian tạo ra một bản sao của đối tượng khi truyền nó vào phương thức như tham số thông thường.

Trong hầu hết các trường hợp, nó không tạo nên sự khác biệt quá nhiều, vì mọi tham số thường là chuỗi hay số nguyên nhưng trong tình huống bạn gọi cùng phương thức nhiều lần trong vòng lặp hay bạn truyền tham số với giá trị lớn như chuỗi lớn hay cấu trúc thì nó có thể cho bạn thấy sự khác biệt về hiệu suất.

Đây là một ví dụ chúng ta dùng in:

public void Main(string[] args)
{
	string aVeryLargeString = "Lots of text...";
	InMethod(aVeryLargeString);
}

public void InMethod(in string largeString)
{
	Console.WriteLine(largeString);
}

Trong phương thức của chúng ta, InMethod(), thì largeString giờ là tham trị chỉ đọc tới biến gốc (aVeryLargeString), thì chúng ta có thể dùng nó nhưng không thay đổi nó. Nếu chúng ta thử thì trình biên dịch sẽ báo lỗi:

public void InMethod(in string largeString)
{
	largeString = "We can't do this...";
}

Error: Cannot assign to variable 'in string' because it is a readonly variable

Named parameters

Như bạn thấy trong toàn bộ ví dụ trên, mỗi tham số có tên riêng trong khai báo phương thức. Nó cho phép bạn trỏ tới tham số bên trong phương thức. Tuy nhiên, khi gọi phương thức, bạn không dùng tên này - bạn chỉ cần cung cấp giá trị theo cùng thứ tự khai báo. Đây không phải là vấn đề cho các phương thức đơn giản với 2-3 tham số nhưng với phương thức phức tạp hơn và nhiều tham số hơn thì sẽ khó hơn để chỉ ra giá trị tương ứng, như:

PrintUserDetails(1, "John", 42, null);

Nó không rõ ràng khi có nhiều tham số nhưng nếu bạn nhìn vào khai báo phương thức thì bạn có thể thấy:

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
}

Nhưng thật phiên phức nếu bạn phải tìm lại khai báo phương thức để hiểu, do đó bạn có thể cung cấp tên tham số trực tiếp trong lời gọi phương thứ. Nó cũng cho phép bạn cung cấp tên tham số ở bất kỳ vị trí nào, trừ phi ép sử dụng có thứ tự. Ví dụ:

PrintUserDetails(name: "John Doe", userId: 1);

Nó cho phép bạn cung cấp giá trị cho bất kỳ tham số tùy chọn nào mà không cần cung cấp giá trị cho toàn bộ tham số tùy chọn trong khai báo. Hay nói cách khác, nếu bạn muốn cung cấp giá trị cho addressLines thì cũng phải cung cấp giá trị cho age. Tuy nhiên, nếu bạn dùng tham số mà không quan trọng thứ tự thì bạn chỉ cần cung cấp tên và giá trị cho nó như sau:

PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);

Đây là một ví dụ hoàn chỉnh dùng tham số đặt tên:

public void Main(string[] args)
{
	PrintUserDetails(1, "John", 42, null);
	PrintUserDetails(name: "John Doe", userId: 1);
	PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
}

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
	Console.WriteLine(name + " is " + age + " years old...");
}

Summary

As you can see from this article, parameters come in many forms and types. Fortunately, you'll come along way with plain, old regular parameters, but once you start to dig deeper into the C# language, you will benefit from knowing all the types and modifiers as explained in this article.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!