Menu

Access modifier C#: public, private, protected và internal

Sáu access modifier trong C# (public, private, protected, internal, protected internal, private protected), mỗi loại cho phép những gì, giá trị mặc định khi bạn không ghi gì, và các lỗi biên dịch chúng gây ra.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Access modifier (phạm vi truy cập) quyết định code nào được dùng một kiểu hay một thành viên. Nhờ chúng, một lớp tách phần mà code khác dựa vào (bề mặt công khai) khỏi phần nó được tự do thay đổi (phần bên trong). C# có sáu access modifier, cộng thêm các giá trị mặc định khi bạn không ghi gì.

Sáu access modifier

ModifierKiểu chứa nóKiểu dẫn xuất, cùng assemblyCode khác, cùng assemblyKiểu dẫn xuất, assembly khácCode khác, assembly khác
publiccócócócócó
protected internalcócócócókhông
protectedcócókhôngcókhông
internalcócócókhôngkhông
private protectedcócókhôngkhôngkhông
privatecókhôngkhôngkhôngkhông

Một assembly là một project đã biên dịch: tệp .dll hoặc .exe mà nó tạo ra. "Cùng assembly" nghĩa là "cùng project", vì vậy internal có tác dụng như "hiển thị với code của tôi, ẩn với người dùng thư viện của tôi".

private và public: tính đóng gói

private là mặc định cho thành viên và là lựa chọn đúng cho hầu hết các trường. Lớp cung cấp những gì bên gọi cần thông qua các phương thức và thuộc tính public, và chính các phương thức đó giữ cho dữ liệu của đối tượng luôn nhất quán.

Đầu ra:

False
True
Priya: 180

Vì balance là private, cách duy nhất để thay đổi nó là Deposit và TryWithdraw, và cả hai đều áp dụng quy tắc. Không có code nào khác trong chương trình có thể tạo ra số dư âm, dù được viết thế nào đi nữa. Đó là lợi ích của tính đóng gói (encapsulation): quy tắc nằm ở một chỗ duy nhất.

private áp dụng theo kiểu, không theo đối tượng. Một phương thức của Account có thể đọc other.balance trên một instance Account khác, và các phương thức Equals hay phương thức so sánh thường được viết theo cách này.

protected: hiển thị với lớp dẫn xuất

Thành viên protected được ẩn với bên ngoài nhưng dùng được trong các lớp kế thừa từ lớp khai báo nó.

Đầu ra:

Sam: 4000.00
Rita: 6400.00

Có một quy tắc khiến nhiều người bất ngờ. Bên trong Manager, bạn có thể dùng baseSalary trên this hoặc trên một Manager khác, nhưng không phải trên một Employee bất kỳ:

class Manager : Employee
{
    public decimal Compare(Employee other)
    {
        return baseSalary - other.baseSalary;
        // error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
        // of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
    }
}

Lý do: other có thể là một Contractor cũng dẫn xuất từ Employee, và protected chỉ cấp quyền truy cập vào nhánh của chính bạn trong cây kế thừa, không phải nhánh của các lớp anh em.

Một trường protected ràng buộc mọi lớp dẫn xuất vào trường đó mãi mãi. Nhiều codebase thích dùng một thuộc tính hoặc phương thức protected hơn, hoặc giữ trường ở private và chỉ đưa cho lớp dẫn xuất đúng những gì chúng cần.

internal: hiển thị trong assembly

Thành viên và kiểu internal có thể được dùng bởi mọi code trong cùng project và không gì bên ngoài dùng được. Một thư viện dùng nó cho các lớp hỗ trợ mà các lớp public của nó dựa vào:

// In the MyShop.Pricing library project
public class PriceCalculator
{
    public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}

internal static class TaxRules   // callers of the library cannot see this class
{
    internal static decimal Apply(decimal amount) => amount * 1.2m;
}

Kiểu cấp cao nhất là internal khi bạn không ghi modifier. Đó là lý do một lớp bạn quên đánh dấu public trong một class library lại "biến mất" khỏi project tham chiếu đến nó.

Project unit test là các assembly riêng, nên chúng cũng không nhìn thấy code internal. Cách xử lý chuẩn là thêm một attribute trong thư viện:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]

protected internal và private protected

Hai modifier này kết hợp các modifier trước theo hai cách ngược nhau, và tên của chúng rất dễ nhầm:

  • protected internal là protected hoặc internal: mọi code trong cùng assembly, cộng thêm các lớp dẫn xuất ở assembly khác. Đây là loại rộng hơn.
  • private protected (C# 7.2) là protected và internal: chỉ các lớp dẫn xuất cũng nằm trong cùng assembly. Đây là loại hẹp hơn, dành cho lớp cơ sở có các điểm mở rộng chỉ nên nằm trong thư viện của bạn.
public class ReportBase
{
    protected internal string Title;      // same assembly, or subclasses anywhere
    private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}

Mặc định khi bạn không ghi gì

Vị tríMặc địnhĐược phép
Kiểu khai báo trong namespaceinternalpublic, internal, file (C# 11)
Thành viên của classprivatecả sáu
Thành viên của structprivatepublic, internal, private (struct không thể bị kế thừa)
Kiểu lồng nhauprivatecả sáu
Thành viên của interfacepublicđược ghi modifier rõ ràng từ C# 8
Thành viên của enumpublickhông có

Ghi rõ modifier chỉ tốn một từ nhưng giúp người đọc sau khỏi phải thắc mắc, nên hầu hết các bộ quy tắc viết code đều yêu cầu ghi modifier kể cả khi nó trùng với mặc định.

Phạm vi truy cập ở cấp accessor

Một thuộc tính hoặc indexer có thể cho một accessor modifier hẹp hơn chính thuộc tính đó:

Đầu ra:

report.pdf: 100%

Modifier của accessor phải chặt hơn modifier của thuộc tính, và chỉ một trong hai accessor được có modifier riêng.

Phạm vi truy cập không nhất quán

Một thành viên public không thể để lộ một kiểu có phạm vi hẹp hơn chính nó, vì bên gọi sẽ nhận về một thứ mà họ không được phép gọi tên:

internal class Discount { }

public class Checkout
{
    public Discount Current() => null;
    // error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}

Quy tắc tương tự gây lỗi CS0051 cho kiểu tham số và CS0053 cho kiểu thuộc tính. Cách sửa là cho kiểu đó hiển thị rộng bằng thành viên, hoặc thu hẹp thành viên lại bằng kiểu.

Chọn modifier nào

Hãy bắt đầu với modifier chặt nhất mà vẫn chạy được, và chỉ nới rộng khi có bên gọi cần đến. Mở rộng phạm vi về sau thì dễ và không bao giờ làm hỏng code của ai; thu hẹp phạm vi thì làm hỏng mọi nơi đang dùng nó. Trong thực tế: trường để private, các thành viên thể hiện mục đích của lớp để public, các hàm hỗ trợ để private, và các kiểu chỉ dùng trong thư viện để internal.

Câu hỏi thường gặp

C# có những access modifier nào?

Có sáu loại: public (mọi code), private (chỉ kiểu chứa nó), protected (kiểu chứa nó và các kiểu dẫn xuất từ đó), internal (mọi code trong cùng assembly), protected internal (cùng assembly, hoặc kiểu dẫn xuất ở bất kỳ đâu) và private protected (kiểu dẫn xuất trong cùng assembly, C# 7.2). C# 11 bổ sung file cho các kiểu chỉ hiển thị trong một tệp mã nguồn.

Access modifier mặc định trong C# là gì?

Thành viên của class hoặc struct (trường, phương thức, thuộc tính, kiểu lồng nhau) mặc định là private. Kiểu cấp cao nhất (class khai báo trực tiếp trong namespace) mặc định là internal. Thành viên của interface và của enum là public.

protected trong C# có nghĩa là gì?

Thành viên protected hiển thị bên trong chính lớp của nó và trong mọi lớp dẫn xuất từ lớp đó, nhưng không hiển thị với code khác. Lớp dẫn xuất chỉ dùng được nó thông qua chính kiểu của mình: bên trong Manager, this.baseSalary dùng được, nhưng truy cập baseSalary trên một đối tượng Employee khác sẽ gây lỗi CS1540.

internal trong C# là gì?

internal khiến một kiểu hoặc thành viên hiển thị với mọi code trong cùng assembly (tệp .dll hoặc .exe biên dịch từ cùng một project) và ẩn với bên ngoài. Đây là cách một thư viện giữ các lớp hỗ trợ ngoài API công khai của mình. [assembly: InternalsVisibleTo("MyLib.Tests")] cho phép project kiểm thử cũng nhìn thấy chúng.

Làm sao sửa lỗi "is inaccessible due to its protection level"?

Đó là lỗi CS0122: code đang dùng một thành viên mà nó không được phép thấy, thường gặp nhất là một trường để mặc định private. Nếu code bên ngoài cần đọc giá trị đó, hãy cung cấp một thuộc tính hoặc phương thức public; đừng chỉ đơn giản đổi trường thành public. Nếu đó là một kiểu từ project khác, nhiều khả năng kiểu đó là internal.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu