# Access Modifier trong C#: public, private, protected, internal khác nhau như thế nào?
Khi làm việc với C#, một trong những kiến thức nền tảng nhưng rất dễ bị nhầm là **Access Modifier**.
Access Modifier quyết định:
> Một class, method, property hoặc field có thể được truy cập từ đâu.
Trong C#, chúng ta thường gặp 6 access modifier chính:
– `public`
– `private`
– `protected`
– `internal`
– `protected internal`
– `private protected`
Bài viết này sẽ giải thích từng loại, cách chúng hoạt động, sự khác nhau giữa chúng và các câu hỏi thường gặp khi phỏng vấn .NET.
—
## 1. Access Modifier là gì?
Ví dụ chúng ta có một class:
“`csharp
public class UserService
{
public string Name;
private string _password;
}
“`
Ở đây:
“`csharp
public
“`
và
“`csharp
private
“`
chính là Access Modifier.
Chúng quyết định phạm vi mà một member có thể được truy cập.
Ví dụ:
“`csharp
var service = new UserService();
service.Name = “Vu”;
“`
Có thể truy cập `Name` vì nó là:
“`csharp
public
“`
Nhưng nếu viết:
“`csharp
service._password = “123456”;
“`
sẽ bị lỗi compile vì `_password` là:
“`csharp
private
“`
—
# 2. public
`public` có phạm vi truy cập rộng nhất.
Một member được khai báo là `public` có thể được truy cập từ bất kỳ đâu nếu class chứa nó cũng có thể truy cập được.
Ví dụ:
“`csharp
public class UserService
{
public string GetUserName()
{
return “Vu”;
}
}
“`
Ở class khác:
“`csharp
var service = new UserService();
var name = service.GetUserName();
“`
Hoàn toàn hợp lệ.
Có thể hiểu đơn giản:
“`text
public = truy cập từ mọi nơi
“`
### Khi nào sử dụng?
Thường sử dụng cho:
– API public
– Service method
– DTO property
– Interface implementation
– Method mà các layer khác cần gọi
Ví dụ:
“`csharp
public async Task
{
…
}
“`
—
# 3. private
`private` là phạm vi truy cập hẹp nhất.
Một member `private` chỉ có thể được sử dụng bên trong chính class chứa nó.
Ví dụ:
“`csharp
public class UserService
{
private string GetPassword()
{
return “secret”;
}
}
“`
Trong chính class:
“`csharp
public class UserService
{
private string GetPassword()
{
return “secret”;
}
public void Login()
{
var password = GetPassword();
}
}
“`
Hoàn toàn hợp lệ.
Nhưng từ class khác:
“`csharp
var service = new UserService();
service.GetPassword();
“`
sẽ bị lỗi.
Có thể nhớ:
“`text
private = chỉ trong chính class
“`
### Khi nào nên sử dụng?
Thông thường hãy ưu tiên `private` nếu member không cần được expose ra ngoài.
Ví dụ:
“`csharp
private async Task
{
…
}
“`
Hoặc:
“`csharp
private readonly IUserRepository _userRepository;
“`
Đây là một phần của nguyên tắc encapsulation.
Không nên expose thứ không cần thiết.
—
# 4. protected
`protected` thường xuất hiện khi làm việc với inheritance.
Một member `protected` có thể được truy cập:
– Trong class hiện tại
– Trong class kế thừa
Ví dụ:
“`csharp
public class BaseService
{
protected void Log()
{
Console.WriteLine(“Logging…”);
}
}
“`
Class kế thừa:
“`csharp
public class UserService : BaseService
{
public void CreateUser()
{
Log();
}
}
“`
`UserService` có thể gọi:
“`csharp
Log();
“`
vì nó kế thừa `BaseService`.
Nhưng code bên ngoài không thể gọi:
“`csharp
var service = new UserService();
service.Log();
“`
Có thể nhớ:
“`text
protected = dành cho inheritance
“`
—
# 5. internal
`internal` là một modifier khá quan trọng trong .NET.
Một class hoặc member `internal` chỉ có thể được truy cập bên trong cùng một assembly.
Ví dụ:
“`csharp
internal class UserRepository
{
}
“`
Các class khác trong cùng project có thể sử dụng:
“`csharp
var repository = new UserRepository();
“`
Nhưng nếu một project khác reference project này thì:
“`csharp
new UserRepository();
“`
sẽ không truy cập được.
—
## Assembly là gì?
Trong thực tế có thể hiểu đơn giản:
“`text
1 project .NET → build ra 1 assembly
“`
Ví dụ solution:
“`text
MyApplication
│
├── MyApplication.API
├── MyApplication.Application
├── MyApplication.Domain
└── MyApplication.Infrastructure
“`
Sau khi build có thể tạo ra:
“`text
MyApplication.API.dll
MyApplication.Application.dll
MyApplication.Domain.dll
MyApplication.Infrastructure.dll
“`
Mỗi DLL là một assembly.
Nếu:
“`csharp
internal class UserRepository
{
}
“`
nằm trong:
“`text
MyApplication.Infrastructure
“`
thì mặc định project:
“`text
MyApplication.API
“`
không thể truy cập class đó trực tiếp.
Có thể nhớ:
“`text
internal = same assembly
“`
—
# 6. protected internal
Đây là một trong những modifier dễ gây nhầm nhất.
“`csharp
protected internal
“`
không có nghĩa là:
“`text
protected AND internal
“`
Mà là:
“`text
protected OR internal
“`
Tức là có thể truy cập khi:
– Cùng assembly
HOẶC
– Là class kế thừa
Ví dụ:
“`csharp
public class BaseService
{
protected internal void Execute()
{
}
}
“`
Trong cùng assembly:
“`csharp
var service = new BaseService();
service.Execute();
“`
Có thể truy cập.
Nếu ở assembly khác nhưng kế thừa:
“`csharp
public class UserService : BaseService
{
public void Run()
{
Execute();
}
}
“`
cũng có thể truy cập.
Có thể nhớ:
“`text
protected internal
=
protected OR internal
“`
—
# 7. private protected
`private protected` được thêm vào C# nhằm giới hạn access chặt hơn.
Nó có nghĩa là:
“`text
protected AND internal
“`
Tức là member chỉ được truy cập khi:
– Class kế thừa
– Và class đó nằm trong cùng assembly
Ví dụ:
“`csharp
public class BaseService
{
private protected void Execute()
{
}
}
“`
Nếu class kế thừa nằm cùng assembly:
“`csharp
public class UserService : BaseService
{
public void Run()
{
Execute();
}
}
“`
Hợp lệ.
Nhưng nếu `UserService` nằm ở assembly khác thì:
“`csharp
Execute();
“`
sẽ không truy cập được.
Có thể nhớ:
“`text
private protected
=
protected AND internal
“`
—
# 8. So sánh tất cả Access Modifier
| Modifier | Same Class | Derived Class | Same Assembly | Other Assembly |
|—|—:|—:|—:|—:|
| public | Yes | Yes | Yes | Yes |
| private | Yes | No | No | No |
| protected | Yes | Yes | Không trực tiếp | Qua inheritance |
| internal | Yes | Yes | Yes | No |
| protected internal | Yes | Yes | Yes | Nếu inheritance |
| private protected | Yes | Yes | Nếu inheritance | No |
Có thể nhớ nhanh:
“`text
public
= everywhere
private
= same class
protected
= inheritance
internal
= same assembly
protected internal
= protected OR internal
private protected
= protected AND internal
“`
—
# 9. Top-level class dùng được modifier nào?
Một câu khá hay gặp trong phỏng vấn.
Top-level class không thể khai báo:
“`csharp
private class UserService
{
}
“`
hoặc:
“`csharp
protected class UserService
{
}
“`
Thông thường top-level class chỉ sử dụng:
“`csharp
public
“`
hoặc:
“`csharp
internal
“`
Ví dụ:
“`csharp
public class UserService
{
}
“`
hoặc:
“`csharp
internal class UserRepository
{
}
“`
Nếu không khai báo modifier:
“`csharp
class UserRepository
{
}
“`
thì mặc định class top-level là:
“`csharp
internal
“`
—
# 10. Nested class thì sao?
Nested class là class nằm bên trong một class khác.
Ví dụ:
“`csharp
public class UserService
{
private class UserValidator
{
}
}
“`
Trong trường hợp này có thể dùng:
“`csharp
private
“`
vì `UserValidator` là nested class.
Ví dụ:
“`csharp
public class UserService
{
private class UserValidator
{
public bool Validate()
{
return true;
}
}
}
“`
—
# 11. Default access modifier trong C#
Đây cũng là câu hỏi phỏng vấn phổ biến.
## Top-level class
Nếu viết:
“`csharp
class UserService
{
}
“`
mặc định là:
“`csharp
internal class UserService
{
}
“`
—
## Member bên trong class
Ví dụ:
“`csharp
public class UserService
{
string Name;
}
“`
`Name` mặc định là:
“`csharp
private string Name;
“`
Tương tự:
“`csharp
public class UserService
{
void Execute()
{
}
}
“`
tương đương:
“`csharp
public class UserService
{
private void Execute()
{
}
}
“`
—
# 12. Access Modifier và Encapsulation
Access Modifier liên quan rất nhiều đến nguyên lý:
“`text
Encapsulation
“`
trong Object-Oriented Programming.
Một class không nên expose toàn bộ state của nó ra bên ngoài.
Ví dụ không tốt:
“`csharp
public class BankAccount
{
public decimal Balance;
}
“`
Code bên ngoài có thể làm:
“`csharp
account.Balance = -1000000;
“`
Điều này phá vỡ business rule.
Tốt hơn:
“`csharp
public class BankAccount
{
public decimal Balance { get; private set; }
public void Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException();
if (amount > Balance)
throw new InvalidOperationException();
Balance -= amount;
}
}
“`
Ở đây:
“`csharp
private set;
“`
giúp bảo vệ state của object.
Code bên ngoài chỉ có thể đọc:
“`csharp
account.Balance
“`
nhưng không thể:
“`csharp
account.Balance = 999999;
“`
—
# 13. Một nguyên tắc quan trọng khi thiết kế class
Một nguyên tắc khá hữu ích:
> Expose ít nhất có thể.
Nếu một method chỉ sử dụng nội bộ thì để:
“`csharp
private
“`
Nếu chỉ dành cho inheritance:
“`csharp
protected
“`
Nếu chỉ sử dụng trong assembly:
“`csharp
internal
“`
Chỉ sử dụng:
“`csharp
public
“`
khi thật sự cần expose contract ra ngoài.
Ví dụ:
“`csharp
public class PaymentService
{
public async Task ProcessPaymentAsync()
{
await ValidatePaymentAsync();
await CreateTransactionAsync();
await SendNotificationAsync();
}
private Task ValidatePaymentAsync()
{
…
}
private Task CreateTransactionAsync()
{
…
}
private Task SendNotificationAsync()
{
…
}
}
“`
Consumer chỉ cần biết:
“`csharp
ProcessPaymentAsync()
“`
Không cần biết implementation bên trong.
—
# 14. Access Modifier trong kiến trúc nhiều layer
Giả sử project có:
“`text
API
Application
Domain
Infrastructure
“`
Có thể sử dụng `internal` để hạn chế implementation.
Ví dụ:
“`csharp
public interface IUserRepository
{
Task
}
“`
Interface cần được expose nên để:
“`csharp
public
“`
Implementation:
“`csharp
internal class UserRepository : IUserRepository
{
}
“`
Bên ngoài không cần biết implementation cụ thể.
Dependency Injection sẽ resolve nó.
Ví dụ:
“`csharp
services.AddScoped
“`
Cách này giúp giảm coupling.
—
# 15. public không phải lúc nào cũng tốt
Một lỗi phổ biến là developer để mọi thứ thành:
“`csharp
public
“`
Ví dụ:
“`csharp
public class OrderService
{
public void ValidateOrder() { }
public void CalculateTotal() { }
public void SaveOrder() { }
public void PublishEvent() { }
}
“`
Trong khi consumer chỉ cần:
“`csharp
CreateOrder()
“`
Thiết kế tốt hơn:
“`csharp
public class OrderService
{
public void CreateOrder()
{
ValidateOrder();
CalculateTotal();
SaveOrder();
PublishEvent();
}
private void ValidateOrder() { }
private void CalculateTotal() { }
private void SaveOrder() { }
private void PublishEvent() { }
}
“`
Public API của class trở nên nhỏ và rõ ràng hơn.
—
# 16. Access Modifier khác với các modifier khác
Ngoài access modifier, C# còn có rất nhiều modifier khác như:
“`csharp
static
abstract
sealed
virtual
override
readonly
const
partial
async
extern
“`
Nhưng chúng không quyết định phạm vi truy cập.
Ví dụ:
“`csharp
public static class UserHelper
{
}
“`
Trong đó:
“`text
public
“`
là access modifier.
Còn:
“`text
static
“`
là non-access modifier.
Hoặc:
“`csharp
public sealed class UserService
{
}
“`
`public` quyết định khả năng truy cập.
`sealed` quyết định class có được kế thừa hay không.
—
# 17. Một số câu hỏi phỏng vấn thường gặp
## Câu 1: private và protected khác nhau như thế nào?
`private` chỉ truy cập được trong chính class.
`protected` có thể được truy cập trong class và class kế thừa.
—
## Câu 2: internal là gì?
`internal` giới hạn phạm vi truy cập trong cùng assembly.
—
## Câu 3: protected internal nghĩa là gì?
Nó là:
“`text
protected OR internal
“`
Có thể truy cập nếu cùng assembly hoặc thông qua inheritance.
—
## Câu 4: private protected nghĩa là gì?
Nó là:
“`text
protected AND internal
“`
Chỉ class kế thừa trong cùng assembly mới truy cập được.
—
## Câu 5: class không khai báo access modifier thì mặc định là gì?
Top-level class mặc định:
“`csharp
internal
“`
—
## Câu 6: member không khai báo access modifier thì mặc định là gì?
Member trong class mặc định:
“`csharp
private
“`
—
## Câu 7: Có thể khai báo top-level class là private không?
Không.
Ví dụ sau không hợp lệ:
“`csharp
private class UserService
{
}
“`
Nhưng nested class có thể là `private`.
—
# 18. Cheat Sheet để nhớ nhanh
“`text
public
→ Anywhere
private
→ Same class
protected
→ Same class + derived classes
internal
→ Same assembly
protected internal
→ Same assembly OR derived class
private protected
→ Same assembly AND derived class
“`
Một cách nhớ khác:
“`text
protected internal
↓
OR
private protected
↓
AND
“`
—
# Kết luận
Access Modifier không chỉ là syntax của C#.
Nó ảnh hưởng trực tiếp đến:
– Encapsulation
– API design
– Maintainability
– Coupling
– Architecture
– Security của object state
Khi thiết kế class, đừng mặc định để mọi thứ là `public`.
Thay vào đó hãy hỏi:
> Member này thực sự cần được truy cập từ đâu?
Nếu chỉ dùng trong class:
“`csharp
private
“`
Nếu dành cho inheritance:
“`csharp
protected
“`
Nếu chỉ dùng trong cùng assembly:
“`csharp
internal
“`
Nếu cần expose ra ngoài:
“`csharp
public
“`
Một public API càng nhỏ và rõ ràng thì code càng dễ kiểm soát và maintain.