Skip to content

Commit 0e3e811

Browse files
CopilotBillWagner
andauthored
Improve clarity of protected access modifier documentation with unified examples and comparison table (#47117)
* Initial plan * Improve clarity of protected access modifier documentation with unified examples and comparison table Co-authored-by: BillWagner <[email protected]> * Move inline code examples to snippet files for protected keyword documentation Co-authored-by: BillWagner <[email protected]> * Replace !code-csharp with ::: directive for code snippets in protected.md Co-authored-by: BillWagner <[email protected]> * Replace old code reference with ::: directive and remove unnecessary project file Co-authored-by: BillWagner <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: BillWagner <[email protected]>
1 parent 3af99cf commit 0e3e811

File tree

7 files changed

+113
-7
lines changed

7 files changed

+113
-7
lines changed

docs/csharp/language-reference/keywords/private-protected.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ The `private protected` keyword combination is a member access modifier. A priva
1818
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:
1919

2020
```csharp
21+
// Assembly1.cs
22+
// Compile with: /target:library
2123
public class BaseClass
2224
{
2325
private protected int myValue = 0;
@@ -54,12 +56,26 @@ class DerivedClass2 : BaseClass
5456
```
5557

5658
This example contains two files, `Assembly1.cs` and `Assembly2.cs`.
57-
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.
59+
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.
5860

59-
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.
61+
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).
6062

6163
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.
6264

65+
## Comparison with other protected access modifiers
66+
67+
The following table summarizes the key differences between the three protected access modifiers:
68+
69+
| Access Modifier | Same Assembly, Derived Class | Same Assembly, Non-derived Class | Different Assembly, Derived Class |
70+
|---|:-:|:-:|:-:|
71+
| `protected` | ✔️ || ✔️ |
72+
| `protected internal` | ✔️ | ✔️ | ✔️ |
73+
| `private protected` | ✔️ |||
74+
75+
- Use `protected` when you want derived classes in any assembly to access the member
76+
- Use `protected internal` when you want the most permissive access (any class in same assembly OR derived classes anywhere)
77+
- Use `private protected` when you want the most restrictive protected access (only derived classes in the same assembly)
78+
6379
Struct members cannot be `private protected` because the struct cannot be inherited.
6480

6581
## C# language specification

docs/csharp/language-reference/keywords/protected-internal.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ class DerivedClass : BaseClass
5353
```
5454

5555
This example contains two files, `Assembly1.cs` and `Assembly2.cs`.
56-
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.
57-
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.
56+
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.
57+
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.
5858

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

docs/csharp/language-reference/keywords/protected.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,36 @@ For a comparison of `protected` with the other access modifiers, see [Accessibil
2424

2525
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:
2626

27-
[!code-csharp[csrefKeywordsModifiers#11](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsModifiers/CS/csrefKeywordsModifiers.cs#11)]
27+
:::code language="csharp" source="./snippets/protected/Example1.cs" id="snippet1":::
2828

29-
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.
29+
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.
30+
31+
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.
3032

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

3335
## Example 2
3436

3537
In this example, the class `DerivedPoint` is derived from `Point`. Therefore, you can access the protected members of the base class directly from the derived class.
3638

37-
[!code-csharp[csrefKeywordsModifiers#12](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsModifiers/CS/csrefKeywordsModifiers.cs#12)]
39+
:::code language="csharp" source="./snippets/protected/Example2.cs" id="snippet1":::
3840

3941
If you change the access levels of `x` and `y` to [private](private.md), the compiler will issue the error messages:
4042

4143
`'Point.y' is inaccessible due to its protection level.`
4244

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

47+
## Cross-assembly access
48+
49+
The following example demonstrates that `protected` members are accessible from derived classes even when they're in different assemblies:
50+
51+
:::code language="csharp" source="./snippets/protected/Assembly1.cs" id="snippet1":::
52+
53+
:::code language="csharp" source="./snippets/protected/Assembly2.cs" id="snippet1":::
54+
55+
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).
56+
4557
## C# language specification
4658

4759
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.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//<snippet1>
2+
// Assembly1.cs
3+
// Compile with: /target:library
4+
namespace Assembly1
5+
{
6+
public class BaseClass
7+
{
8+
protected int myValue = 0;
9+
}
10+
}
11+
//</snippet1>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//<snippet1>
2+
// Assembly2.cs
3+
// Compile with: /reference:Assembly1.dll
4+
namespace Assembly2
5+
{
6+
using Assembly1;
7+
8+
class DerivedClass : BaseClass
9+
{
10+
void Access()
11+
{
12+
// OK, because protected members are accessible from
13+
// derived classes in any assembly
14+
myValue = 10;
15+
}
16+
}
17+
}
18+
//</snippet1>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//<snippet1>
2+
namespace Example1
3+
{
4+
class BaseClass
5+
{
6+
protected int myValue = 123;
7+
}
8+
9+
class DerivedClass : BaseClass
10+
{
11+
static void Main()
12+
{
13+
var baseObject = new BaseClass();
14+
var derivedObject = new DerivedClass();
15+
16+
// Error CS1540, because myValue can only be accessed through
17+
// the derived class type, not through the base class type.
18+
// baseObject.myValue = 10;
19+
20+
// OK, because this class derives from BaseClass.
21+
derivedObject.myValue = 10;
22+
}
23+
}
24+
}
25+
//</snippet1>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//<snippet1>
2+
namespace Example2
3+
{
4+
class Point
5+
{
6+
protected int x;
7+
protected int y;
8+
}
9+
10+
class DerivedPoint: Point
11+
{
12+
static void Main()
13+
{
14+
var dpoint = new DerivedPoint();
15+
16+
// Direct access to protected members.
17+
dpoint.x = 10;
18+
dpoint.y = 15;
19+
Console.WriteLine($"x = {dpoint.x}, y = {dpoint.y}");
20+
}
21+
}
22+
// Output: x = 10, y = 15
23+
}
24+
//</snippet1>

0 commit comments

Comments
 (0)