Skip to content

Improve clarity of protected access modifier documentation with unified examples and comparison table #47117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions docs/csharp/language-reference/keywords/private-protected.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ The `private protected` keyword combination is a member access modifier. A priva
A private protected member of a base class is accessible from derived types in its containing assembly only if the static type of the variable is the derived class type. For example, consider the following code segment:

```csharp
// Assembly1.cs
// Compile with: /target:library
public class BaseClass
{
private protected int myValue = 0;
Expand Down Expand Up @@ -54,12 +56,26 @@ class DerivedClass2 : BaseClass
```

This example contains two files, `Assembly1.cs` and `Assembly2.cs`.
The first file contains a public base class, `BaseClass`, and a type derived from it, `DerivedClass1`. `BaseClass` owns a private protected member, `myValue`, which `DerivedClass1` tries to access in two ways. The first attempt to access `myValue` through an instance of `BaseClass` will produce an error. However, the attempt to use it as an inherited member in `DerivedClass1` will succeed.
The first file contains a public base class, `BaseClass`, and a type derived from it, `DerivedClass1`. `BaseClass` owns a private protected member, `myValue`, which `DerivedClass1` can access as an inherited member within the same assembly.

In the second file, an attempt to access `myValue` as an inherited member of `DerivedClass2` will produce an error, as it is only accessible by derived types in Assembly1.
In the second file, an attempt to access `myValue` as an inherited member of `DerivedClass2` will produce an error, because `private protected` members are only accessible by derived types **within the same assembly**. This is the key difference from `protected` (which allows access from derived classes in any assembly) and `protected internal` (which allows access from any class within the same assembly or derived classes in any assembly).

If `Assembly1.cs` contains an <xref:System.Runtime.CompilerServices.InternalsVisibleToAttribute> that names `Assembly2`, the derived class `DerivedClass2` will have access to `private protected` members declared in `BaseClass`. `InternalsVisibleTo` makes `private protected` members visible to derived classes in other assemblies.

## Comparison with other protected access modifiers

The following table summarizes the key differences between the three protected access modifiers:

| Access Modifier | Same Assembly, Derived Class | Same Assembly, Non-derived Class | Different Assembly, Derived Class |
|---|:-:|:-:|:-:|
| `protected` | ✔️ | ❌ | ✔️ |
| `protected internal` | ✔️ | ✔️ | ✔️ |
| `private protected` | ✔️ | ❌ | ❌ |

- Use `protected` when you want derived classes in any assembly to access the member
- Use `protected internal` when you want the most permissive access (any class in same assembly OR derived classes anywhere)
- Use `private protected` when you want the most restrictive protected access (only derived classes in the same assembly)

Struct members cannot be `private protected` because the struct cannot be inherited.

## C# language specification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class DerivedClass : BaseClass
```

This example contains two files, `Assembly1.cs` and `Assembly2.cs`.
The first file contains a public base class, `BaseClass`, and another class, `TestAccess`. `BaseClass` owns a protected internal member, `myValue`, which is accessed by the `TestAccess` type.
In the second file, an attempt to access `myValue` through an instance of `BaseClass` will produce an error, while an access to this member through an instance of a derived class, `DerivedClass` will succeed.
The first file contains a public base class, `BaseClass`, and another class, `TestAccess`. `BaseClass` owns a protected internal member, `myValue`, which is accessed by the `TestAccess` type because they're in the same assembly.
In the second file, an attempt to access `myValue` through an instance of `BaseClass` will produce an error, while an access to this member through an instance of a derived class, `DerivedClass` will succeed. This shows that `protected internal` allows access from **any class within the same assembly** or **derived classes in any assembly**, making it the most permissive of the protected access modifiers.

Struct members cannot be `protected internal` because the struct cannot be inherited.

Expand Down
16 changes: 14 additions & 2 deletions docs/csharp/language-reference/keywords/protected.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ For a comparison of `protected` with the other access modifiers, see [Accessibil

A protected member of a base class is accessible in a derived class only if the access occurs through the derived class type. For example, consider the following code segment:

[!code-csharp[csrefKeywordsModifiers#11](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsModifiers/CS/csrefKeywordsModifiers.cs#11)]
[!code-csharp[snippet1](~/docs/csharp/language-reference/keywords/snippets/protected/Example1.cs#snippet1)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace all instances of !code-csharp with the ::: directive for code snippets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced all instances of !code-csharp with the ::: directive for code snippets in the protected.md file. Commit: a9c6c61


The statement `a.x = 10` generates an error because it accesses the protected member through a base class reference (`a` is of type `A`). Protected members can only be accessed through the derived class type or types derived from it.
The statement `baseObject.myValue = 10` generates an error because it accesses the protected member through a base class reference (`baseObject` is of type `BaseClass`). Protected members can only be accessed through the derived class type or types derived from it.

Unlike `private protected`, the `protected` access modifier allows access from derived classes **in any assembly**. Unlike `protected internal`, it does **not** allow access from non-derived classes within the same assembly.

Struct members cannot be protected because the struct cannot be inherited.

Expand All @@ -42,6 +44,16 @@ If you change the access levels of `x` and `y` to [private](private.md), the com

`'Point.x' is inaccessible due to its protection level.`

## Cross-assembly access

The following example demonstrates that `protected` members are accessible from derived classes even when they're in different assemblies:

[!code-csharp[snippet1](~/docs/csharp/language-reference/keywords/snippets/protected/Assembly1.cs#snippet1)]

[!code-csharp[snippet1](~/docs/csharp/language-reference/keywords/snippets/protected/Assembly2.cs#snippet1)]

This cross-assembly accessibility is what distinguishes `protected` from `private protected` (which restricts access to the same assembly) but is similar to `protected internal` (though `protected internal` also allows same-assembly access from non-derived classes).

## C# language specification

For more information, see [Declared accessibility](~/_csharpstandard/standard/basic-concepts.md#752-declared-accessibility) in the [C# Language Specification](~/_csharpstandard/standard/README.md). The language specification is the definitive source for C# syntax and usage.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//<snippet1>
// Assembly1.cs
// Compile with: /target:library
namespace Assembly1
{
public class BaseClass
{
protected int myValue = 0;
}
}
//</snippet1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//<snippet1>
// Assembly2.cs
// Compile with: /reference:Assembly1.dll
namespace Assembly2
{
using Assembly1;

class DerivedClass : BaseClass
{
void Access()
{
// OK, because protected members are accessible from
// derived classes in any assembly
myValue = 10;
}
}
}
//</snippet1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//<snippet1>
namespace Example1
{
class BaseClass
{
protected int myValue = 123;
}

class DerivedClass : BaseClass
{
static void Main()
{
var baseObject = new BaseClass();
var derivedObject = new DerivedClass();

// Error CS1540, because myValue can only be accessed through
// the derived class type, not through the base class type.
// baseObject.myValue = 10;

// OK, because this class derives from BaseClass.
derivedObject.myValue = 10;
}
}
}
//</snippet1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Loading