C# Access Modifiers / Specifiers

In C#, access modifiers or access specifiers are keywords used to specify the visibility or accessibility of types and members (methods, properties, fields, etc.) in a class or struct. C# provides several access modifiers that control the accessibility of these elements. Here are the access modifiers in C#:

  1. public: The public access modifier allows the type or member to be accessible from any other code in the same assembly or referencing assemblies.
  2. private: The private access modifier limits the accessibility of a type or member to the containing class or struct. It means that private members are only accessible from within the same class or struct.
  3. protected: The protected access modifier allows access to the type or member from within the same class or struct and any derived classes. It is similar to the private modifier but allows derived classes to access the member.
  4. internal: The internal access modifier allows access to the type or member within the same assembly but not from other assemblies. It is useful when you want to make certain elements accessible only within a specific assembly.
  5. protected internal: The protected internal access modifier combines the behavior of protected and internal. It allows access from the same assembly and derived classes, whether they are in the same assembly or a different assembly.
  6. private protected: The private protected access modifier is a combination of private and protected. It restricts access to the containing class and derived classes within the same assembly.

These access modifiers can be applied to classes, structs, interfaces, methods, properties, fields, events, and more. By using the appropriate access modifier, you can control the visibility and accessibility of types and members to ensure proper encapsulation and data hiding in your C# code.

1) C# Public Access Specifier:

In C#, the public access specifier is used to declare types (classes, structs, enums, delegates) and members (methods, properties, fields, events) that are accessible from any code within the same assembly or referencing assemblies.

Here are a few key points about the public access specifier:

  1. Accessibility: Public types and members are accessible from anywhere in the code, both within the same assembly and in other assemblies that reference the assembly containing the public element.
  2. Inheritance: Public members are inherited by derived classes, and they retain their accessibility level in the derived class. For example, if a base class has a public method, it can be accessed by instances of the derived class.
  3. Encapsulation: Public access breaks encapsulation to some extent, as it allows unrestricted access to the type or member. This means that any code can call a public method, access a public property, or modify a public field.
  4. Assembly-level access: If a type or member is marked as public and is included in a public assembly, it can be accessed by any code that references that assembly.

Here’s an example that demonstrates the use of the public access specifier:

public class MyClass
{
    public int PublicField;
    
    public void PublicMethod()
    {
        // Code for the public method
    }
}

public static void Main()
{
    MyClass myObject = new MyClass();
    
    // Accessing the public field
    myObject.PublicField = 10;
    
    // Calling the public method
    myObject.PublicMethod();
}

In the example above, the MyClass type is marked as public, which means it can be accessed from anywhere in the code. The PublicField and PublicMethod are also marked as public, allowing external code to access and modify the field and call the method.

By using the public access specifier, you make types and members widely accessible, which can be useful when you want them to be used by other parts of your code or by external assemblies.

2) C# Protected Access Specifier:

In C#, the protected access specifier is used to declare members (methods, properties, fields, events) that are accessible within the same class or struct and by derived classes. It restricts access to only the containing class and its derived classes.

Here are some important points about the protected access specifier:

  1. Accessibility: Protected members are accessible from within the same class or struct and any derived classes, regardless of whether they are in the same assembly or a different assembly.
  2. Inheritance: Protected members are inherited by derived classes, allowing them to access and override those members. This enables derived classes to reuse and extend the behavior of the base class.
  3. Encapsulation: Protected access provides a level of encapsulation by allowing only the containing class and its derived classes to access the member. Other classes or code outside the inheritance hierarchy cannot access protected members.
  4. Instance-specific: Protected members are tied to instances of the class or struct. Derived classes can access protected members of their base class through instances of the derived class.

Here’s an example that demonstrates the use of the protected access specifier:

public class MyBaseClass
{
    protected int ProtectedField;
    
    protected void ProtectedMethod()
    {
        // Code for the protected method
    }
}

public class MyDerivedClass : MyBaseClass
{
    public void AccessProtectedMember()
    {
        ProtectedField = 10; // Accessing the protected field
        ProtectedMethod();   // Calling the protected method
    }
}

public static void Main()
{
    MyDerivedClass derivedObject = new MyDerivedClass();
    
    derivedObject.AccessProtectedMember();
}

In the example above, MyBaseClass contains a protected field called ProtectedField and a protected method called ProtectedMethod. The MyDerivedClass class derives from MyBaseClass and is able to access and use these protected members.

The AccessProtectedMember method in MyDerivedClass demonstrates accessing the protected members inherited from the base class.

By using the protected access specifier, you can provide access to members that are specific to a class’s inheritance hierarchy, ensuring encapsulation and controlled visibility within that hierarchy.

3) C# Internal Access Specifier:

n C#, the internal access specifier is used to declare types (classes, structs, enums, delegates) and members (methods, properties, fields, events) that are accessible within the same assembly but not from other assemblies.

Here are some important points about the internal access specifier:

  1. Accessibility: Internal types and members are accessible within the same assembly. They can be accessed by any code within the assembly, including other types and members.
  2. Assembly-level access: The internal access specifier is useful when you want to expose types and members only within a specific assembly. It provides a level of encapsulation by restricting access to other assemblies.
  3. Inaccessible from other assemblies: Code in other assemblies cannot directly access internal types and members. If a type or member is marked as internal, it will not be visible to code in other assemblies, even if the assembly is referenced.
  4. Friend assemblies: It’s worth noting that the internal access can be extended to allow access from specific friend assemblies using the InternalsVisibleTo attribute. This attribute allows you to specify which other assemblies can access the internal types and members.

Here’s an example that demonstrates the use of the internal access specifier:

// Assembly A
internal class MyClass
{
    internal int InternalField;
    
    internal void InternalMethod()
    {
        // Code for the internal method
    }
}

// Assembly B
public class AnotherClass
{
    public void AccessInternalMember()
    {
        MyClass myObject = new MyClass();
        myObject.InternalField = 10; // Accessing the internal field
        myObject.InternalMethod();   // Calling the internal method
    }
}

In the example above, MyClass is marked as internal, meaning it can only be accessed within the same assembly (Assembly A). The InternalField and InternalMethod are also marked as internal, allowing code within Assembly A to access and use them.

The AnotherClass in Assembly B demonstrates accessing the internal members of MyClass. Although AnotherClass is in a different assembly, it cannot directly access MyClass or its internal members.

By using the internal access specifier, you can control the visibility of types and members within a single assembly, allowing you to hide implementation details from other assemblies while still providing access within the same assembly.

4) C# Protected Internal Access Specifier:

In C#, the protected internal access specifier combines the behavior of both protected and internal. It allows access to types and members from within the same assembly, as well as by derived classes, whether they are in the same assembly or a different assembly.

Here are some important points about the protected internal access specifier:

  1. Accessibility: protected internal types and members are accessible within the same assembly and by derived classes, whether they are in the same assembly or a different assembly.
  2. Inheritance: Derived classes, whether they are in the same assembly or a different assembly, can access and override protected internal members inherited from the base class.
  3. Assembly-level access: protected internal members can be accessed from any code within the same assembly. However, outside the assembly, access is limited to derived classes.
  4. Inaccessible by unrelated code: Code that is not derived from the base class and is not in the same assembly cannot directly access protected internal members.

Here’s an example that demonstrates the use of the protected internal access specifier:

// Assembly A
public class MyBaseClass
{
    protected internal int ProtectedInternalField;
    
    protected internal void ProtectedInternalMethod()
    {
        // Code for the protected internal method
    }
}

// Assembly B
public class MyDerivedClass : MyBaseClass
{
    public void AccessProtectedInternalMember()
    {
        ProtectedInternalField = 10;      // Accessing the protected internal field
        ProtectedInternalMethod();        // Calling the protected internal method
    }
}

// Assembly C
public class UnrelatedClass
{
    public void AccessProtectedInternalMember()
    {
        MyBaseClass myObject = new MyBaseClass();
        myObject.ProtectedInternalField = 20;      // Cannot access directly, as it's not derived from MyBaseClass
        myObject.ProtectedInternalMethod();        // Cannot access directly, as it's not derived from MyBaseClass
    }
}

In the example above, MyBaseClass contains a protected internal field called ProtectedInternalField and a protected internal method called ProtectedInternalMethod. These members can be accessed by code within the same assembly (Assembly A) as well as derived classes in other assemblies, such as MyDerivedClass in Assembly B.

The AccessProtectedInternalMember method in MyDerivedClass demonstrates accessing the protected internal members inherited from the base class.

On the other hand, the UnrelatedClass in Assembly C cannot directly access protected internal members of MyBaseClass because it is neither derived from MyBaseClass nor in the same assembly.

By using the protected internal access specifier, you can provide access to members that are specific to derived classes and accessible within the same assembly, while still maintaining a level of encapsulation and restricting access by unrelated code.

5) C# Private Access Specifier:

In C#, the private access specifier is used to declare members (methods, properties, fields, events) that are accessible only within the same class or struct. It restricts access to only the containing class or struct and does not allow access from derived classes or other code.

Here are some important points about the private access specifier:

  1. Accessibility: Private members are accessible only within the same class or struct where they are defined. They cannot be accessed from outside the class or struct, including derived classes or other code.
  2. Encapsulation: Private access provides the highest level of encapsulation by restricting access to only the containing class or struct. It helps to hide implementation details and internal logic from other parts of the code.
  3. Inheritance: Private members are not inherited by derived classes. Derived classes cannot access private members of their base class.
  4. Instance-specific: Private members are tied to instances of the class or struct. Each instance has its own private members that are not accessible from other instances.

Here’s an example that demonstrates the use of the private access specifier:

public class MyClass
{
    private int privateField;
    
    private void PrivateMethod()
    {
        // Code for the private method
    }
    
    public void AccessPrivateMember()
    {
        privateField = 10;   // Accessing the private field within the same class
        PrivateMethod();     // Calling the private method within the same class
    }
}

public static void Main()
{
    MyClass myObject = new MyClass();
    
    // The following lines will generate compilation errors
    myObject.privateField = 20;      // Cannot access private field from outside the class
    myObject.PrivateMethod();        // Cannot call private method from outside the class
}

In the example above, MyClass contains a private field called privateField and a private method called PrivateMethod. These members are accessible only within the same class (MyClass). The AccessPrivateMember method demonstrates accessing the private members from within the class itself.

However, in the Main method, when attempting to access the private field and call the private method on an instance of MyClass, compilation errors occur because private members cannot be accessed from outside the class.

By using the private access specifier, you ensure that members are only accessible within the class or struct where they are defined, enforcing strong encapsulation and maintaining the integrity of the class’s internal implementation.