# 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 GetUserAsync(Guid userId)
{
…
}
“`

—

# 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 ValidateUserAsync()
{
…
}
“`

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 GetByIdAsync(Guid id);
}
“`

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.