C# Private Protected: Syntax and Usage
c# private protected: Learn how C# private protected restricts access to derived types within the same assembly, with syntax, examples, and common pitfalls.
C# private protected is an access modifier that combines the inheritance restriction of protected with the assembly restriction of internal. A member declared private protected is accessible only from derived classes that are defined in the same assembly. This gives you a precise way to expose members to a controlled set of subclasses without making them visible to the entire assembly or to all derived types across assemblies.
What Does private protected Actually Mean?
The modifier was introduced in C# 7.2. It is a conjunction of two rules:
- The member must be accessed through a derived class (like
protected). - The derived class must be in the same assembly as the member's declaring class (like
internal).
In code:
public class Base { private protected int Value { get; set; } } public class DerivedInSameAssembly : Base { public void SetValue() => Value = 42; // OK } public class NonDerivedInSameAssembly { public void TryAccess(Base b) => b.Value = 42; // Error: cannot access private protected member }
A class derived from Base in another assembly cannot access Value either, even though it inherits it. The member is effectively hidden from that derived class because the assembly check fails.
How It Differs from protected internal
A common source of confusion is protected internal, which is an OR combination: a member is accessible from any derived class (regardless of assembly) OR from any type in the same assembly. private protected is an AND combination: both conditions must be true.
| Modifier | Access from derived class in same assembly | Access from derived class in another assembly | Access from non-derived type in same assembly |
|---|---|---|---|
protected | Yes | Yes | No |
internal | Yes | No | Yes |
protected internal | Yes | Yes | Yes |
private protected | Yes | No | No |
The table shows that private protected is the most restrictive of the four when you need inheritance-based access. It is useful when you want to share implementation details only with subclasses that live in the same library or module, while preventing external consumers from seeing those details even if they subclass the type.
Practical Usage in a Base Class
Consider a library that provides a base class for plugin developers. You want to allow derived classes to override a certain behavior, but you do not want that behavior to be part of the public API. If you use protected, any derived class in any assembly can access it, which might be too broad. If you use internal, non-derived classes in the same assembly can also access it, which might break encapsulation. private protected gives you the middle ground.
public abstract class PluginBase { private protected virtual void OnStart() { } } public class MyPlugin : PluginBase { protected override void OnStart() { base.OnStart(); // Custom startup logic } }
Here, OnStart is only visible to derived classes within the same assembly. External plugins that inherit from PluginBase cannot override or call OnStart because they are in a different assembly. This allows the library author to change the internal lifecycle without breaking external subclasses.
Overriding and Virtual Members
When you override a private protected virtual member, the override must also be private protected (or a more restrictive modifier, but private protected is already the most restrictive that allows overriding). The override is still subject to the same assembly and derived-class constraints. This means that even if a derived class in another assembly tries to override a private protected member, it will fail because the member is not visible to it.
// In assembly A public class Base { private protected virtual void Hook() { } } // In assembly B public class Derived : Base { // Error: cannot access private protected member protected override void Hook() { } }
The compiler enforces this at compile time. This is a deliberate design choice to prevent external assemblies from relying on internal extension points that may change.
When to Use private protected
Use private protected when you are designing a class hierarchy within a single assembly and you want to restrict certain members to derived classes only, but also keep them out of the public surface area. Typical scenarios include:
- Internal framework hooks that should be overridable by subclasses within the same library.
- Template method patterns where the steps are not meant to be called from outside the inheritance chain.
- Avoiding exposing members that are only relevant to a specific subclass family.
If you are building a public API that external developers will extend, private protected is usually not the right choice because it prevents external subclasses from accessing the members. In that case, protected is more appropriate.
Common Pitfalls and Limitations
One pitfall is assuming that private protected makes a member accessible to all derived classes in the same assembly, regardless of the inheritance path. The rule is that the access must be through a derived type. For example, a base class method that takes a parameter of the base type cannot access a private protected member on that parameter if the method is not in a derived class.
public class Base { private protected int Secret; } public class Derived : Base { public void Access(Base other) { other.Secret = 5; // Error: cannot access private protected member through base reference } }
Even though Derived is a subclass, it can only access Secret through an instance of Derived (or a further derived type), not through a reference to Base. This is the same rule as protected access.
Another limitation is that private protected cannot be applied to top-level types; it only applies to members of a class or struct. Also, it is not allowed on interfaces because interface members are implicitly public.
Maintainability and API Design Considerations
From an API design perspective, private protected helps you keep your public contract small while still allowing internal extensibility. However, it adds a subtle dependency on assembly boundaries. If you later split your assembly into multiple assemblies, the accessibility changes and may break derived classes. This is a maintainability tradeoff: you gain encapsulation at the cost of making the assembly structure part of your API contract.
When documenting your code, be explicit about the intended use of private protected members. Because they are not visible to external consumers, they won't appear in IntelliSense for them, but they will appear for internal derived classes. This can be confusing if developers are not aware of the assembly constraint.
Interaction with Multi-Level Inheritance
In a multi-level inheritance hierarchy, private protected members remain accessible to any derived class in the same assembly, regardless of how many levels down the hierarchy they are. However, the access is still restricted to derived types. Consider:
public class GrandBase { private protected void Helper() { } } public class Parent : GrandBase { public void CallHelper() => Helper(); // OK } public class Child : Parent { public void CallHelperToo() => Helper(); // OK }
Both Parent and Child can access Helper because they are derived from GrandBase and reside in the same assembly. But a class that is not derived from GrandBase cannot access it, even if it is in the same assembly. This behavior is consistent with the modifier's definition and is useful when you want to propagate an internal extension point across a deep hierarchy without exposing it to unrelated types.