Understanding Access Modifiers in C#

Access Modifiers in C# define how accessible a class, method, field, property, or other class members are to other program parts. They are essential for implementing encapsulation, one of the key principles of Object-Oriented Programming (OOP).

Types of Access Modifiers

  1. Public
  2. Private
  3. Protected
  4. Internal
  5. Protected Internal
  6. Private Protected

1. Access Modifiers: Public

The public access modifier in C# specifies that a member (such as a field, property, method, or class) is accessible from any other part of the program, without restrictions. When a member is declared public, it can be accessed directly by code outside of the class or assembly in which it is defined.

  • Full Accessibility: Members with public access can be accessed by other classes, including those in different namespaces or assemblies.
  • Use Case: public is often used for members that need to be visible and usable across the application, such as APIs, utility functions, or data meant for interaction.
  • Risks: Using public can lead to better encapsulation, making your code harder to maintain and more prone to unintended modifications.
  • Good Practices: Combine public with proper validation or read-only mechanisms, such as using properties instead of public fields, to ensure better control over how members are accessed or modified.

Example

class Person
{
    public string Name; // Can be accessed from outside the class
}
class Program
{
    static void Main()
    {
        Person person = new Person();
        person.Name = "Mayooran"; // Accessing public field
        Console.WriteLine(person.Name);
    }
}

This code demonstrates a simple class named Person with a single public field, Name, which can be accessed and modified outside the class. In the Program class, the Main method creates an instance of the Person class and assigns the Name field the value "Mayooran". It then uses the Console.WriteLine method to print the value of the Name field to the console. This demonstrates how to declare a public field, instantiate a class, and interact with its members. However, exposing fields to the public is generally discouraged in favor of using properties for better encapsulation and control.

Encapsulation

Image 01: Person Can be accessed from outside the class

2. Access Modifiers: Private

The private access modifier in C# restricts access to members of a class to the class itself. This is the most restrictive access level, ensuring that the member can only be accessed and used within the class where it is defined.

  1. Restricted Access: Members declared as private are accessible only within the same class. They are hidden from other classes, including derived (child) classes.
  2. Encapsulation: private is often used to enforce encapsulation, a key principle of object-oriented programming. This allows internal details of a class to remain hidden and prevents external code from directly modifying sensitive data or internal logic.
  3. Use Case: It is commonly used for internal variables, helper methods, or logic that should not be exposed to the outside world.
  4. Access via Public Methods/Properties: Private members are typically exposed to external code through public properties or methods, providing controlled access and validation.

Example

class BankAccount
{
    private decimal balance; // Private field
    public void Deposit(decimal amount)
    {
        if (amount > 0)
            balance += amount; // Accessible within the class
    }
    public decimal GetBalance() => balance; // Exposes value securely
}
class Program
{
    static void Main()
    {
        BankAccount account = new BankAccount();
        account.Deposit(1000); // Access public method
        // Console.WriteLine(account.balance); // Error: balance is private
        Console.WriteLine(account.GetBalance());
    }
}

This code demonstrates a simple implementation of encapsulation in a BankAccount class. The class includes a private field, balance, which is used to store the account balance securely. Since the balance is marked as private, it cannot be accessed or modified directly from outside the class. Instead, the Deposit method is provided to safely add a specified amount to the balance, with a condition ensuring only positive values are accepted. To retrieve the balance, the class exposes a GetBalance method, which returns the value securely without allowing direct manipulation.

In the Program class, the Main method creates an instance of the BankAccount class and uses the Deposit method to add money to the account. The attempt to access the balance field directly would result in an error, illustrating the protection provided by the private access modifier. Finally, the GetBalance method is called to display the current balance. This design adheres to object-oriented programming principles by encapsulating data and providing controlled access through public methods.

Such encapsulation ensures that the class's internal state remains consistent and minimizes the risk of unintended modifications. It also allows for additional logic, such as validation, to be implemented within the class methods without exposing the underlying fields to the calling code. This approach makes the BankAccount class secure and easier to maintain.

Account.balance

Image 02: Balance cannot be accessed from outside the class (Console.WriteLine(account.balance); // Error: balance is private)

3. Access Modifiers: Protected

The protected access modifier in C# allows a class member to be accessible only within its class and by derived (child) classes. It strikes a balance between encapsulation and flexibility, enabling derived classes to reuse and extend the functionality of the base class.

  1. Access in Base and Derived Classes: Members declared as protected can be accessed within the class where they are declared and in any class that inherits from it.
  2. Not Accessible Outside the Hierarchy: Code outside the class hierarchy, including instances of the class, cannot access protected members directly.
  3. Use Case: protected is used when you want to hide implementation details from outside the class but still allow inheriting classes to reuse or customize functionality.
  4. Encapsulation with Flexibility: It provides encapsulation but allows controlled extension of a class’s functionality through inheritance.

Example

class Animal
{
    protected void Eat() => Console.WriteLine("Eating...");
}
class Dog : Animal
{
    public void BarkAndEat()
    {
        Console.WriteLine("Barking...");
        Eat(); // Accessible because Dog inherits Animal
    }
}
class Program
{
    static void Main()
    {
        Dog dog = new Dog();
        dog.BarkAndEat();
        // dog.Eat(); // Error: Eat is protected
    }
}

This code demonstrates the use of the protected access modifier, which allows base class members to be accessed by derived classes but not by external code. The Animal class defines a protected field, Species, and a protected method, DisplaySpecies. The Species field is initialized through the Animal constructor.

The Dog class inherits from Animal and uses the base class constructor to set the Species field to "Dog." It also calls the DisplaySpecies method from the base class to display the species of the animal. The Speak method extends the functionality by adding a message specific to the Dog class.

In the Main method, an instance of the Dog class is created, and its Speak method is called, which successfully uses the protected members of the Animal class. Direct access to the Species field or DisplaySpecies method from the Main method is not allowed, ensuring encapsulation within the class hierarchy. This illustrates how protected members enable controlled reuse and extension of base class functionality in derived classes.

 Eat method

Image 03: The Eat method cannot be accessed from outside the class dog.Eat(); // Error: Eat is protected

4. Access Modifiers: Internal

The internal access modifier in C# specifies that a member or type is accessible only within the same assembly. It provides a way to share functionality across multiple classes and files within an assembly while keeping it hidden from external assemblies.

  1. Assembly-Level Access: Members or types declared as internal can only be accessed by code that resides in the same assembly.
  2. Not Accessible Outside the Assembly: Code in other assemblies, including derived classes, cannot access internal members unless marked with the InternalsVisibleTo attribute (for unit testing or trusted assemblies).
  3. Use Case: Commonly used for components or utilities that need to interact with multiple classes within the same assembly but should not be exposed as part of the public API.
  4. Default for Non-Nested Classes: If no access modifier is specified for a top-level class, it is internal by default.

Example

File 01. MyLibrary

namespace MyLibrary
{
    internal class InternalHelper
    {
        internal string GetInternalMessage()
        {
            return "This is an internal message from MyLibrary.";
        }
    }
    public class PublicAPI
    {
        public string FetchMessage()
        {
            InternalHelper helper = new InternalHelper();
            return helper.GetInternalMessage(); // Internal member accessed within the same assembly
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            PublicAPI api = new PublicAPI();
            string message = api.FetchMessage();
            Console.WriteLine(message);
        }
    }
}

File 02. Access_Modifiers_C_

using MyLibrary;

namespace Access_Modifiers_C_
{
    class Program
    {
        static void Main()
        {
            // Attempting to access the internal class directly will result in an error
            // InternalHelper helper = new InternalHelper(); // Error: InternalHelper is inaccessible

            PublicAPI api = new PublicAPI();
            Console.WriteLine(api.FetchMessage()); // Access internal functionality through the public API
        }
    }
}

This code demonstrates,

  1. InternalHelper Class: Declared as internal within the MyLibrary assembly, so it can only be accessed within the MyLibrary assembly.
  2. PublicAPI Class: Declared as public and provides a public method, FetchMessage, which internally uses the InternalHelper class. This design allows controlled exposure of internal functionality.
  3. External Access in ConsumerApp: Direct access to InternalHelper or its methods from the ConsumerApp assembly is restricted, as it is marked internal. Instead, the ConsumerApp accesses the PublicAPI class, which acts as a gateway to the internal functionality.

PublicApi

Image 04.1: MyLibrary solution has PublicApi

MyLibrary 

Image 04.2: PublicApi object access from MyLibrary

5. Access Modifiers: Protected Internal

The protected internal access modifier in C# is a combination of two access levels: protected and internal. It allows members to be accessed either by derived classes or within the same assembly. This provides a flexible access scope that balances encapsulation and extensibility.

  1. Access Within the Same Assembly: Members with protected internal access can be accessed by any class within the same assembly, even if they are not derived.
  2. Access by Derived Classes: Members can also be accessed by derived classes, even if those derived classes are in a different assembly.
  3. Combination of Two Scopes: The modifier combines the benefits of protected (hierarchy access) and internal (assembly access). It is essentially an OR condition.
  4. Use Case: This modifier is ideal when you want to expose functionality to derived classes for extension and to other classes within the same assembly for internal use.

Example

class Vehicle
{
    protected internal void StartEngine() => Console.WriteLine("Engine started!");
}
class Car : Vehicle
{
    public void Drive() => StartEngine(); // Accessible due to inheritance
}
class Program
{
    static void Main()
    {
        Vehicle vehicle = new Vehicle();
        vehicle.StartEngine(); // Accessible in the same assembly
    }
}

This code demonstrates the use of the protected internal access modifier, allowing the StartEngine method in the Vehicle class to be accessed within the same assembly or by derived classes. The StartEngine method outputs a message indicating that the engine has started.

The Car class inherits from Vehicle and uses the StartEngine method in its Drive method. This is possible because protected internal allows access to inherited members within derived classes, even when the derived class is in a different assembly.

In the Main method, an instance of the Vehicle class is created, and its StartEngine method is called directly. This is allowed because the method is accessible within the same assembly due to the internal portion of protected internal.

If the StartEngine method were accessed from a non-derived class in a different assembly, it would result in a compilation error. This restriction enforces encapsulation while still allowing flexibility for inheritance and same-assembly access.

This code illustrates how protected internal provides controlled access for both class hierarchies and internal usage, making it useful in scenarios where shared and extensible functionality is needed.

PublicApi object access

Image 05: PublicApi object access from MyLibrary

6. Access Modifiers: Private Protected

The private protected access modifier in C# is a combination of two access levels: private and protected. It restricts access to members within the same class, derived classes, and the same assembly. This provides tighter control compared to protected internal.

  1. Limited Scope
    • Members are accessible only within the containing class and derived classes in the same assembly.
    • Derived classes outside the assembly cannot access private protected members.
  2. Combination of Two Scopes: Combines the restrictive nature of private (limited to the class) and protected (accessible in derived classes), but only within the assembly.
  3. Use Case: Ideal when you want to allow inheritance within the same assembly but keep the functionality hidden from external assemblies.

Example

class BaseClass
{
    private protected void Display() => Console.WriteLine("Private Protected method!");
}
class DerivedClass : BaseClass
{
    public void CallDisplay() => Display(); // Accessible in derived class
}
class Program
{
    static void Main()
    {
        // BaseClass baseObj = new BaseClass();
        // baseObj.Display(); // Error: Not accessible outside BaseClass or its derived classes
    }
}

This code demonstrates the use of the private protected access modifier, which restricts member access to the containing class (BaseClass) and derived classes within the same assembly. The Display method in the BaseClass is marked as private protected, so it can only be accessed by derived classes like DerivedClass within the same assembly.

The derived class inherits from BaseClass and contains the CallDisplay method, which internally calls the Display method. This is allowed because the private protected modifier grants access to members within derived classes in the same assembly.

In the Main method, an attempt to directly access the Display method through an instance of BaseClass would result in a compilation error. This is because private protected members cannot be accessed outside the class hierarchy, even within the same assembly.

This setup ensures that sensitive functionality, such as the Display method, remains hidden from external access but is still usable by inheriting classes for internal purposes. The private protected modifier thus combines strict encapsulation with controlled extensibility.

Overall, this code highlights how the private protected access level enforces both class-level privacy and limited inheritance access within the same assembly.

 Access modifier 

Image 06: Private protected access modifier

Essential Insights on Access Modifiers

Essential Insights

  1. Encapsulation: Access modifiers play a vital role in encapsulation, allowing developers to hide implementation details and expose only the necessary parts of a class or its members.
  2. Flexibility: The variety of access modifiers (private, protected, internal, protected internal, private protected) ensures flexibility, making it possible to define access at levels ranging from strict class-only (private) to broader scopes like assemblies (internal) or inheritance hierarchies (protected).
  3. Controlled Inheritance: Modifiers like protected, protected internal, and private protected control how and where inheritance can extend a base class, striking a balance between sharing functionality and maintaining control.
  4. Assembly-Specific Access: The internal and protected internal modifiers ensure that members can be shared across the same assembly while still hiding them from external assemblies.
  5. Choosing the Right Modifier
    • Private: Use when members should only be accessible within the class.
    • protected: Use when members should be accessible in derived classes.
    • internal: Use for members that should only be accessible within the same assembly.
    • protected internal: Use for members that need access from derived classes and the same assembly.
    • private protected: Use for members that should be accessible in derived classes but only within the same assembly.

Conclusion

Access modifiers are a fundamental feature of C# for managing access and ensuring that code is secure, maintainable, and scalable. By using them effectively, developers can enforce proper boundaries, reduce bugs, and design more robust and modular systems. Always choose the most restrictive modifier that meets your requirements to ensure good encapsulation and maintainability.


Recommended Free Ebook
Similar Articles
Orfium Sri Lanka
Globally based Software Developing & Data Processing Company