Back to Blog
C#

C# Protected vs Internal: Choosing the Right Access Modifier

c# protected vs internal: Compare C# protected and internal access modifiers: what each controls, how the combined forms work, and when to use each in real code.

C#access modifiersencapsulationobject-oriented programming.NET
Diagram showing C# protected access limited to derived classes and internal access limited to the same assembly.

Choosing between c# protected vs internal comes down to one question: who should be allowed to see this member. Both modifiers restrict access, but they do so along different axes. protected restricts access to the declaring class and its derived types. internal restricts access to code in the same assembly. Understanding that distinction matters because the two are often used in the same codebase for very different reasons.

What protected Actually Controls

A protected member is accessible from the class that declares it and from any class that derives from that class, regardless of which assembly the derived class lives in. The access is granted through the derived type itself, not through a base-class reference.

public class Report { protected string BuildHeader() { return "=== Report ==="; } } public class SalesReport : Report { public string Render() { return BuildHeader() + "\nQ3 sales figures"; } }

SalesReport.Render can call BuildHeader because SalesReport derives from Report. If BuildHeader were private, this call would fail to compile. The key constraint is that the call must happen inside the derived class's own code. You cannot call a protected member on a Report instance from unrelated code, and you cannot call it through a Report reference even from within a derived class.

What internal Actually Controls

An internal member is accessible from any code in the same assembly, but not from code in other assemblies. Inheritance does not matter here. A class in the same assembly can access the member even if it has no relationship to the declaring class; a derived class in another assembly cannot access it.

internal class ReportStore { public string ConnectionString { get; set; } } public class ReportService { public void Save() { var store = new ReportStore(); store.ConnectionString = "Server=prod;Database=reports"; } }

ReportService can instantiate ReportStore and set its property because both live in the same assembly. If ReportStore were public but ConnectionString were internal, the same access rule would apply to the property: same-assembly code can read and write it, and external code cannot.

The Combined Modifiers

C# provides two combined forms that developers often confuse with the individual modifiers.

protected internal means "protected OR internal." A member with this modifier is accessible from derived classes in any assembly, and also from any code in the same assembly. It is the union of the two access levels.

private protected (C# 7.2+) means "protected AND internal." A member with this modifier is accessible only from derived classes that also live in the same assembly. It is the intersection.

public class BaseDocument { protected internal string Metadata { get; set; } private protected string AuditTrail { get; set; } } public class DerivedInSameAssembly : BaseDocument { public void Touch() { Metadata = "doc-42"; // OK: derived class AuditTrail = "created"; // OK: derived class in same assembly } }

A derived class in a different assembly can access Metadata but not AuditTrail, because AuditTrail requires both conditions to hold.

Where Developers Commonly Get Confused

One recurring mistake is assuming protected members are accessible through a base-class reference inside a derived class. They are not.

public class DerivedReport : Report { public void Render(Report other) { // Compile error: cannot access protected member through // a base-class reference string header = other.BuildHeader(); } }

The call fails because the access is mediated by an instance of the base type, not by the derived type. The compiler enforces that protected access happens through the derived type or a further derived type.

Another confusion point: internal does not prevent inheritance. A public class with an internal constructor can still be derived from in another assembly, but the derived class cannot call the base constructor. This often surfaces as a compile error that developers misattribute to the class being sealed.

Choosing Between protected and internal

The decision is driven by who needs the access.

Use protected when the member is part of the extension surface for subclasses. A protected virtual method is a deliberate extension point that derived classes can override or call. This is common in template-method patterns where the base class orchestrates a workflow and subclasses supply steps.

Use internal when the member is an implementation detail shared across classes within the same assembly. Internal APIs let you split logic across multiple classes without exposing those details to consumers of the assembly. This is common in library code where the public surface should stay small.

The two are not interchangeable because they answer different questions. protected answers "can a subclass use this?" internal answers "can other code in this assembly use this?" A member that needs both audiences should use protected internal, and a member that needs only the overlap should use private protected.

Maintainability and Testing Implications

The choice has practical consequences for testing and evolution. internal members can be exposed to a test assembly using InternalsVisibleTo, which lets unit tests access internal APIs without making them public. This is a common pattern for testing internal helpers or for white-box testing of behavior that should not be part of the public contract.

protected members, by contrast, are part of the inheritance contract. Once you expose a protected member, any derived class can depend on it, and removing it later is a breaking change for subclasses. This is especially relevant for library authors: every protected member becomes part of the API surface that consumers can rely on.

A practical guideline: prefer internal for implementation details that cross class boundaries, and reserve protected for members that genuinely need to be overridden or called by subclasses. If a member is only used within the declaring class, keep it private. If it is used by other classes in the assembly, make it internal. If it is needed by derived classes outside the assembly, make it protected. The combined modifiers cover the cases where both conditions apply.

c# protected vs internal: Practical Usage and Code Examples | RYUSLOG DEV