Back to Blog
C#

C# Protected Internal: Union Access Explained

c# protected internal: Learn how C# protected internal combines protected and internal access, when to use it, and how it differs from private protected.

C# access modifiersprotected internalC# inheritanceC# visibilityobject-oriented design
Diagram showing C# protected internal access combining derived class and same-assembly visibility

c# protected internal requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The protected internal access modifier in C# combines two visibility rules into one. A member declared with protected internal is accessible from derived classes and from any code in the same assembly. The two rules are joined with an OR, not an AND. That distinction matters because it changes who can call the member in ways that are easy to misread.

What protected internal Actually Grants

When you write:

public class BaseService { protected internal void Log(string message) { // ... } }

The Log method is reachable from three places:

  1. Inside BaseService itself.
  2. Inside any class that derives from BaseService, regardless of which assembly that derived class lives in.
  3. Inside any type in the same assembly, even if that type does not inherit from BaseService.

The third rule is the one developers often forget. Because internal is part of the modifier, a completely unrelated class in the same assembly can call Log. The protected part does not restrict that access; it only adds the derived-class rule on top.

How It Differs from protected Alone

A plain protected member is visible only to the declaring class and to derived types. A class in the same assembly that does not inherit from the declaring class cannot access it. With protected internal, that same non-derived class gains access because of the internal half.

This is a practical difference in library design. If you ship a class library and mark a member protected, only subclasses can use it. If you mark it protected internal, any code inside your library assembly can also use it, but external code still needs to subclass to reach it.

How It Differs from internal Alone

An internal member is visible to all code in the same assembly, but not to derived classes in other assemblies. Adding protected extends that visibility: a derived class in another assembly can now access the member, even though ordinary code in that other assembly cannot.

The subtle part is that the derived class does not need to be in the same assembly. The protected half grants cross-assembly access through inheritance, while the internal half grants same-assembly access without inheritance.

The Union, Not the Intersection

The name protected internal reads like both conditions must hold, but C# treats it as a union. A caller needs to satisfy only one of the two rules:

  • Be a derived class, or
  • Be in the same assembly.

If C# used an intersection, the member would be accessible only to derived classes that also live in the same assembly. That behavior exists, but under a different name: private protected, introduced in C# 7.2.

public class BaseService { protected internal void Log(string message) { } private protected void Trace(string message) { } }

In this example, Log is visible to any derived class anywhere and to any type in the same assembly. Trace is visible only to derived classes that are also in the same assembly. A derived class in another assembly cannot call Trace, even though it can call Log.

Where protected internal Fits in a Real Codebase

The modifier is most useful when you have an assembly that contains several related types cooperating internally, while still allowing external subclasses to extend behavior.

Consider a plugin-style architecture where a base class lives in a shared assembly and plugins are compiled into separate assemblies:

public abstract class PluginBase { protected internal void RegisterHandler(string name, Action handler) { // Internal registry update } }

Inside the host assembly, the registration infrastructure can call RegisterHandler directly. A plugin assembly can subclass PluginBase and call RegisterHandler from its own code because it is a derived class. Code in the plugin assembly that does not subclass PluginBase cannot call it.

That pattern keeps the registration logic centralized while allowing both internal infrastructure and external extensions to use it.

Common Mistakes with protected internal

The most frequent mistake is assuming the modifier requires both conditions. Developers new to the modifier sometimes expect that a member marked protected internal is hidden from unrelated classes in the same assembly. It is not. The internal half makes it visible to the entire assembly.

Another mistake is using protected internal when protected would be sufficient. If the member is only meant for subclasses, the internal half widens visibility to every type in the assembly. That wider surface can make refactoring harder because internal callers become coupled to a member that was designed for inheritance scenarios.

A third mistake is confusing protected internal with private protected in code reviews. The two modifiers look similar, but one is a union and the other is an intersection. A quick way to keep them straight: protected internal is the broader one, private protected is the narrower one.

Maintainability and API Surface Considerations

Every access modifier defines a contract. protected internal creates a larger API surface than protected because it exposes the member to the whole assembly. That is not inherently wrong, but it has consequences:

  • Internal callers can couple to the member, making it harder to change its signature without touching unrelated code.
  • The member becomes part of the assembly's internal API even if it was designed for inheritance.
  • Tooling that analyzes public and protected API surface will include protected internal members, which can affect documentation generation and API compatibility checks.

If you are designing a library, prefer the narrowest modifier that satisfies the requirement. Start with protected and add internal only when you have a concrete internal caller that needs access. If you need the intersection behavior, use private protected instead.

When the Modifier Can Break Across Assemblies

The cross-assembly behavior of protected internal depends on the derived class actually inheriting from the base type. If a plugin assembly defines a class that does not extend PluginBase, that class cannot access RegisterHandler, even though it is in a different assembly. The internal half does not cross assembly boundaries; only the protected half does, and only through inheritance.

There is also a subtle interaction with friend assemblies. If you apply InternalsVisibleTo to expose internal members to a test assembly, that test assembly gains access to protected internal members as well, because the internal half is satisfied. That is usually desirable for testing, but it means the test assembly can call the member without deriving from the base class.

Choosing Between protected, internal, protected internal, and private protected

The decision comes down to who needs access:

ModifierDerived classes in other assembliesSame assembly, non-derivedDerived classes in same assembly
protectedYesNoYes
internalNoYesYes
protected internalYesYesYes
private protectedNoNoYes

Use protected when only subclasses should access the member. Use internal when the whole assembly needs access but external subclasses should not. Use protected internal when both external subclasses and internal code need access. Use private protected when only subclasses in the same assembly should access the member.

The table shows that protected internal is the most permissive of the four. That permissiveness is sometimes necessary, but it should be a deliberate choice rather than a default.

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