diff --git a/docs/csharp/language-reference/keywords/private-protected.md b/docs/csharp/language-reference/keywords/private-protected.md index e4a25999a1f6e..afd1462f87ff9 100644 --- a/docs/csharp/language-reference/keywords/private-protected.md +++ b/docs/csharp/language-reference/keywords/private-protected.md @@ -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; @@ -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 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 diff --git a/docs/csharp/language-reference/keywords/protected-internal.md b/docs/csharp/language-reference/keywords/protected-internal.md index 77ca6fb9aae29..9dae3cdc32e6a 100644 --- a/docs/csharp/language-reference/keywords/protected-internal.md +++ b/docs/csharp/language-reference/keywords/protected-internal.md @@ -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. diff --git a/docs/csharp/language-reference/keywords/protected.md b/docs/csharp/language-reference/keywords/protected.md index b81bbf41809d1..df8f11a27c708 100644 --- a/docs/csharp/language-reference/keywords/protected.md +++ b/docs/csharp/language-reference/keywords/protected.md @@ -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 language="csharp" source="./snippets/protected/Example1.cs" id="snippet1"::: -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. @@ -34,7 +36,7 @@ Struct members cannot be protected because the struct cannot be inherited. 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. -[!code-csharp[csrefKeywordsModifiers#12](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csrefKeywordsModifiers/CS/csrefKeywordsModifiers.cs#12)] +:::code language="csharp" source="./snippets/protected/Example2.cs" id="snippet1"::: If you change the access levels of `x` and `y` to [private](private.md), the compiler will issue the error messages: @@ -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 language="csharp" source="./snippets/protected/Assembly1.cs" id="snippet1"::: + +:::code language="csharp" source="./snippets/protected/Assembly2.cs" id="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. diff --git a/docs/csharp/language-reference/keywords/snippets/protected/Assembly1.cs b/docs/csharp/language-reference/keywords/snippets/protected/Assembly1.cs new file mode 100644 index 0000000000000..0b7bed2d6f86b --- /dev/null +++ b/docs/csharp/language-reference/keywords/snippets/protected/Assembly1.cs @@ -0,0 +1,11 @@ +// +// Assembly1.cs +// Compile with: /target:library +namespace Assembly1 +{ + public class BaseClass + { + protected int myValue = 0; + } +} +// \ No newline at end of file diff --git a/docs/csharp/language-reference/keywords/snippets/protected/Assembly2.cs b/docs/csharp/language-reference/keywords/snippets/protected/Assembly2.cs new file mode 100644 index 0000000000000..8917c01a496a0 --- /dev/null +++ b/docs/csharp/language-reference/keywords/snippets/protected/Assembly2.cs @@ -0,0 +1,18 @@ +// +// 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; + } + } +} +// \ No newline at end of file diff --git a/docs/csharp/language-reference/keywords/snippets/protected/Example1.cs b/docs/csharp/language-reference/keywords/snippets/protected/Example1.cs new file mode 100644 index 0000000000000..ec44b453301c5 --- /dev/null +++ b/docs/csharp/language-reference/keywords/snippets/protected/Example1.cs @@ -0,0 +1,25 @@ +// +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; + } + } +} +// \ No newline at end of file diff --git a/docs/csharp/language-reference/keywords/snippets/protected/Example2.cs b/docs/csharp/language-reference/keywords/snippets/protected/Example2.cs new file mode 100644 index 0000000000000..90f37f99c1214 --- /dev/null +++ b/docs/csharp/language-reference/keywords/snippets/protected/Example2.cs @@ -0,0 +1,24 @@ +// +namespace Example2 +{ + class Point + { + protected int x; + protected int y; + } + + class DerivedPoint: Point + { + static void Main() + { + var dpoint = new DerivedPoint(); + + // Direct access to protected members. + dpoint.x = 10; + dpoint.y = 15; + Console.WriteLine($"x = {dpoint.x}, y = {dpoint.y}"); + } + } + // Output: x = 10, y = 15 +} +// \ No newline at end of file