In this article, we are going to explain different types of access modifiers in C# and what their purpose is.

For the complete navigation of this series check out: C# Back to Basics.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
To download the source code for this article, you can visit our GitHub repository.

Access modifiers specify the accessibility of an object and all of its members in the C# project. Hence, they help enforce encapsulation by limiting the scope of members and protecting them from unintended access or modification. Moreover, all the C# types have access modifiers implemented, even if they are not stated (default access modifier is applied).

Even though this topic is more related to the object-oriented concept, we will talk about it now, thus making it easier to understand the next article about methods, that strongly relies on access modifiers.

Access Modifiers Types

C# provides four types of access modifiers: private, public, protected, internal, and two combinations: protected-internal and private-protected.

Each of these access modifiers provides a different level of accessibility and visibility, and we can use them to control the behavior of our classes and objects.

Let’s look at each access modifier in more detail.

Private Access Modifier

The private access modifier is the most restrictive of the access modifiers. Members declared with the private access modifier are only accessible within the class in which they are declared.

When we mark a class as private, other classes cannot inherit it.

Let’s understand this with the BankAccount class:

public class BankAccount
{
    private int _balance;

    public int GetBalance()
    {
        return _balance;
    }

    public void Deposit(int amount)
    {
        _balance += amount;
    }

    public void Withdraw(int amount)
    {
        if (_balance - amount >= 0)
        {
            _balance -= amount;
        }
    }
}

Here, the _balance field is declared with a private access modifier, meaning that we can only access it within the BankAccount class.

The GetBalance()Deposit(), and Withdraw() methods are public members and hence any class outside BankAccount can also access them:

var account = new BankAccount();

account.Deposit(100);
var balance = account.GetBalance();
            
Console.WriteLine(balance);

However, if we try to set the value of the _balance field, we get a compiler error saying BankAccount.balance is inaccessible due to its protection level:

account._balance // compiler error

The private access modifier is typically used when a member is intended for internal use only, and should not be accessible from other parts of the program. Thus, it allows us to hide the implementation details of a class and protect its members from unintended access or modification.

Public Access Modifier

The public access modifier is the most permissive of the access modifiers. Members declared with the public access modifier are accessible from anywhere in the code, including derived classes.

When we declare a class public, any other class can inherit it.

Let’s create a class to understand this better:

public class Calculator
{
    public int Value { get; set; }
    public int IncrementValue(int value)
    {
        return value + 1;
    }
}

The Calculator class has a Value property and an IncrementValue() method.

Both the Value property and the IncrementValue() method are declared with a public access modifier, so we can access them from outside the Calculator class from the Program class:

var calculator = new Calculator();

calculator.Value = 15;
var result = calculator.IncrementValue(calculator.Value);

Console.WriteLine(result);

Hence, the public access modifier makes it possible to create classes with well-defined public interfaces, that we can easily use and test from the other parts of the code.

Protected Access Modifier

The protected keyword implies that the object is accessible inside the class and in all classes that derive from that class. We will talk in more detail about inheritance in module 2 about object-oriented programming. 

Let’s create a class Shape to understand it:

public class Shape
{
    protected int Width { get; set; }
    protected int Height { get; set; }

    public virtual int GetArea()
    {
        return Width * Height;
    }
}

Here, we declare a Width and a Height property with the protected access modifier, meaning that they are only accessible from within the Shape class and its derived classes.

The GetArea() method is a public method and we mark it as virtual, so a derived class can override it.

Now, let’s create a class that inherits from Shape:

public class Rectangle : Shape
{
    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }
}

The use of the protected access modifier in Shape allows the derived Rectangle class to access the Width and Height fields inherited from the base class:

var rectangle = new Rectangle(10, 5);
            
var area = rectangle.GetArea();
            
Console.WriteLine(area);

Thus, we use the protected access modifier in C# for creating specialized derived classes that can access members of a base class.

Internal Access Modifier

The internal keyword specifies that the object is accessible only inside its own assembly but not in other assemblies.

Members declared with the internal access modifier are not accessible from outside the assembly (project). It also serves as the default if no explicit access modifier is specified for a class or its members.

Let’s create a class to understand this:

internal class Logger
{
    internal string LogMessage(string message)
    {
        return $"Logged at {DateTime.Now}, Message: {message}";
    }
}

Here, we have a LogMessage() method declared with the internal access modifier. This restricts the method access to only within the same assembly:

var logger = new Logger();
            
var messageLog = logger.LogMessage("This is a message");

Console.WriteLine(messageLog);

Now, if we try to access the LogMessage()method outside the current assembly, we get a compilation error.

Let’s add another project named AnotherAssembly to our existing project, and create a class to try to access our Logger class:

public class ExternalLogger
{
    public void ExternalLogMessage(string message)
    {
        var logger = new AccessModifiersInCsharp.Logger();
        logger.LogMessage(message);
    }
}

Here, if we try to instantiate the Logger class, or to access the LogMessage() method that exists in the AccessModifiersInCsharp assembly, we get a compilation error “inaccessible due to its protection level“.

In conclusion, the internal access modifier makes it possible to create classes that are useful within the same assembly but are not part of the public API.

Protected Internal Access Modifier

The protected internal access modifier is a combination of protected and internal. As a result, we can access the protected internal member only in the same assembly or in a derived class in other assemblies (projects).

Let’s modify the existing Logger class:

internal class Logger
{
    protected internal string LogMessage(string message)
    {
        return $"Logged at {DateTime.Now}, Message: {message}";
    }
}

Here, we have modified the LogMessage() method to mark it with protected internal access modifier. 

Now, let’s create a derived class DerivedLogger:

class DerivedLogger : Logger
{
    public string LogMessageFromDerivedClass(string message)
    {
        return $"Derived Log: {LogMessage(message)}";
    }
}

Here, we create a LogMessageFromDerivedClass() method that calls the LogMessage() method of the Logger class.

Since DerivedLogger is a derived class of Logger, it can access the LogMessage() method because it is declared with the protected internal access modifier.

Thus, by using the protected internal access modifier, we can expose members that are needed for customization and derivation, but not for direct use by external code.

Private Protected Access Modifier

The private protected access modifier is a combination of private and protected keywords. We can access members inside the containing class or in a class that derives from a containing class, but only in the same assembly (project). Therefore, if we try to access it from another assembly, we will get an error.

Let’s create a class to understand this better:

public class Employee
{
    private protected int EmployeeId { get; set; }
    private protected string Name { get; set; }
    private protected double Salary { get; set; }
    
    public virtual string GetEmployeeDetails()
    {
        return $"Employee ID: {EmployeeId}, Name: {Name}, Salary: {Salary}";
    }
}

The Employee class has three private protected properties EmployeeIdName, and Salary. We can access these properties from within the same class and any derived classes  in the same project.

Let’s create a Manager class, in which  we derive from Employee:

public class Manager : Employee
{
    public override string GetEmployeeDetails()
    {
        EmployeeId = 1;
        Name = "Jacob";
        Salary = 90000;

        var employee = new Employee();
        employee.EmployeeId = 2;
        
        return $"Manager Details: {base.GetEmployeeDetails()}";
    }
}

The class overrides the GetEmployeeDetails() method to show the employee details. Here, we have the access to the properties of the Employee class as Manager is a derived class within the same assembly.

However, if we try to instantiate the Employee class from here and access the EmployeeId property, we get a compilation error, “Cannot access protected member ‘Employee.EmployeeId’ via a qualifier of type ‘Employee’; the qualifier must be of type ‘Manager’ (or derived from it)”. This is because we are trying to access a protected member of the base class from the derived class through the base class’s instance.

Let’s create another derived class ExternalManager that resides in another assembly than the Employee class:

public class ExternalManager : Employee
{
    public override string GetEmployeeDetails()
    {
        EmployeeId = 3;
        Name = "David";
        Salary = 80000;
        return $"ExternalManager Details: {base.GetEmployeeDetails()}";
    }
}

Here, the ExternalManager class is derived from Employee. However, as we define it in an external assembly, we get a compilation error, “‘Employee.EmployeeId’ is inaccessible due to its protection level”. We encounter similar errors for the other properties Name, and Salary.

Thus, by using private protected access modifier, we can restrict access to certain members of our base classes to only the derived classes within the same assembly, while still allowing those derived classes to inherit and extend the functionality of the base classes.

Conclusion

So, that’s it about access modifiers. In this article, we’ve covered the six access modifiers in C#: public, private, protected, internal, protected internal, and private protected. We’ve learned about what each of these access modifiers does and their usage.

By using the right access modifier, we ensure that our code is secure, easy to use, and maintain. Finally, we can create a clear and consistent structure for our code.

Our next topic is going to be about Methods in C#.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!