Private Protected vs Protected Internal in C
In C#, private protected and
protected internal are compound access modifiers that
control the visibility of class members across different assemblies and
derived classes. The fundamental difference lies in their evaluation
logic: protected internal uses an "OR" relationship,
granting access to code in the same assembly or any derived
class, while private protected uses an "AND" relationship,
restricting access strictly to derived classes within the same
assembly.
Protected Internal (Union / OR Logic)
A member declared with the protected internal modifier
is accessible to:
- Any code inside the same assembly (functioning like
internal). - Any derived class, even if that derived class resides in a
completely different assembly (functioning like
protected).
This modifier is ideal when you want to allow internal utility classes in your library to access a member while simultaneously allowing external consumers of your library to extend and override that member through inheritance.
Private Protected (Intersection / AND Logic)
Introduced in C# 7.2, a member declared with the
private protected modifier is accessible only to:
- Derived classes that are located within the same assembly.
It prevents external assemblies from accessing the member, even if a class in that external assembly derives from the base class. It also prevents non-derived classes within the same assembly from accessing it. This modifier is best suited for internal framework architectures where base functionality should only be shared with internal subclasses.
Summary Comparison Matrix
| Context | protected internal |
private protected |
|---|---|---|
| Same Assembly, Derived Class | Accessible | Accessible |
| Same Assembly, Non-Derived Class | Accessible | Not Accessible |
| Different Assembly, Derived Class | Accessible | Not Accessible |
| Different Assembly, Non-Derived Class | Not Accessible | Not Accessible |
Code Example
// Assembly A
public class BaseClass
{
protected internal int ProtectedInternalField = 1;
private protected int PrivateProtectedField = 2;
}
public class SameAssemblyDerived : BaseClass
{
void AccessMembers()
{
// Both are accessible here
int a = ProtectedInternalField;
int b = PrivateProtectedField;
}
}
public class SameAssemblyNonDerived
{
void AccessMembers(BaseClass obj)
{
// Accessible because it behaves like 'internal'
int a = obj.ProtectedInternalField;
// Compile Error: PrivateProtectedField is only accessible in derived classes
// int b = obj.PrivateProtectedField;
}
}
// Assembly B (references Assembly A)
public class ExternalAssemblyDerived : BaseClass
{
void AccessMembers()
{
// Accessible because it behaves like 'protected'
int a = ProtectedInternalField;
// Compile Error: PrivateProtectedField cannot be accessed from another assembly
// int b = PrivateProtectedField;
}
}