C# > Object-Oriented Programming (OOP) > Classes and Objects > Access Modifiers (public, private, protected, internal)

Access Modifiers in C#

This snippet demonstrates the use of access modifiers (public, private, protected, and internal) in C# classes. Access modifiers control the visibility and accessibility of class members from different parts of the code.

Code Snippet

This code defines a BankAccount class with members demonstrating different access modifiers. AccountHolderName is public, making it accessible from anywhere. _balance is private, so it's only accessible within the BankAccount class. TransactionCount is protected, making it accessible within the BankAccount class and any derived classes like SavingsAccount. AccountNumber is internal, so it is accessible within the same assembly (project). The SavingsAccount class inherits from BankAccount and demonstrates accessing the protected member TransactionCount. The Main method creates instances of both classes and demonstrates how the access modifiers restrict or allow access to different members.

using System;

namespace AccessModifierExample
{
    public class BankAccount
    {
        // Public member: Accessible from anywhere
        public string AccountHolderName { get; set; }

        // Private member: Only accessible within the class
        private decimal _balance;

        // Protected member: Accessible within the class and derived classes
        protected int TransactionCount { get; set; }

        // Internal member: Accessible within the same assembly
        internal string AccountNumber { get; set; }

        public BankAccount(string accountHolderName, string accountNumber, decimal initialBalance)
        {
            AccountHolderName = accountHolderName;
            AccountNumber = accountNumber;
            _balance = initialBalance;
            TransactionCount = 0;
        }

        // Public method to deposit money
        public void Deposit(decimal amount)
        {
            if (amount > 0)
            {
                _balance += amount;
                TransactionCount++;
                Console.WriteLine($"Deposited {amount}. New balance: {_balance}");
            }
            else
            {
                Console.WriteLine("Invalid deposit amount.");
            }
        }

        // Public method to withdraw money
        public void Withdraw(decimal amount)
        {
            if (amount > 0 && _balance >= amount)
            {
                _balance -= amount;
                TransactionCount++;
                Console.WriteLine($"Withdrawn {amount}. New balance: {_balance}");
            }
            else
            {
                Console.WriteLine("Insufficient funds or invalid withdrawal amount.");
            }
        }

        // Private method (only accessible within this class)
        private decimal GetBalance()
        {
            return _balance;
        }

        // Protected method (accessible within this class and derived classes)
        protected void DisplayTransactionCount()
        {
            Console.WriteLine($"Transaction Count: {TransactionCount}");
        }

        // Internal method (accessible within the same assembly)
        internal void SetAccountNumber(string newAccountNumber)
        {
            AccountNumber = newAccountNumber;
        }
    }

    // Derived class demonstrating protected access
    public class SavingsAccount : BankAccount
    {
        public SavingsAccount(string accountHolderName, string accountNumber, decimal initialBalance) : base(accountHolderName, accountNumber, initialBalance)
        {
        }

        public void DisplayTransactionHistory()
        {
            // Accessing protected member TransactionCount from the base class
            Console.WriteLine("Displaying transaction history...");
            DisplayTransactionCount(); // Accessing protected method
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Creating a BankAccount object
            BankAccount account = new BankAccount("John Doe", "12345", 1000);

            // Accessing public members
            Console.WriteLine($"Account Holder: {account.AccountHolderName}");
            account.Deposit(500);
            account.Withdraw(200);

            // The following line would cause a compilation error because _balance is private
            // Console.WriteLine($"Balance: {account._balance}");

            //Creating a SavingsAccount object
            SavingsAccount savingsAccount = new SavingsAccount("Jane Doe", "67890", 500);
            savingsAccount.DisplayTransactionHistory();

            // Accessing internal member within the same assembly
            account.SetAccountNumber("54321");
            Console.WriteLine($"Account Number Updated: {account.AccountNumber}");

            Console.ReadKey();
        }
    }
}

Concepts Behind the Snippet

Access modifiers are a fundamental part of object-oriented programming, providing encapsulation and information hiding. They allow you to control the visibility of class members, preventing unintended modification or access from outside the class. This promotes code maintainability, security, and reduces the risk of errors. Each access modifier serves a specific purpose: public provides unrestricted access, private restricts access to within the class, protected allows access within the class and derived classes, and internal restricts access to within the same assembly.

Real-Life Use Case

Imagine a system for managing employee data. Employee salaries should be private and only accessible through authorized methods like CalculatePaycheck. Employee names, addresses, and other general information could be public for easy access. A manager's access level to certain employee information, like performance reviews, could be protected (accessible only to the manager class derived from employee). Components used only within the employee management module, like database connection strings, might be internal.

Best Practices

  • Favor the most restrictive access modifier possible. Start with private and only increase visibility when necessary.
  • Use properties (with getters and setters) to control access to data fields instead of making fields directly public.
  • Avoid exposing internal implementation details through public members.

Interview Tip

Be prepared to explain the differences between protected and internal access modifiers. Also, understand the concept of assembly-level access and how internal members contribute to modularity.

When to Use Them

  • Use public when a member needs to be accessible from anywhere.
  • Use private when a member should only be accessible within the class itself.
  • Use protected when a member should be accessible within the class and any derived classes.
  • Use internal when a member should only be accessible within the same assembly (project).

Memory Footprint

Access modifiers don't directly affect the memory footprint of an object. They only control visibility and access. The size of an object is determined by its data members, regardless of their access modifiers.

Alternatives

While access modifiers are the primary mechanism for controlling visibility, other techniques like interfaces and abstract classes can also contribute to encapsulation and information hiding. For example, an interface can define a public contract while hiding the specific implementation details within a class.

Pros

  • Improved code maintainability.
  • Reduced risk of errors.
  • Enhanced security by preventing unauthorized access.
  • Increased code reusability.
  • Supports encapsulation and information hiding, core OOP principles.

Cons

  • Overuse of private can lead to excessively complex code.
  • Incorrect use of access modifiers can hinder extensibility.
  • Requires careful planning and design to ensure proper encapsulation.

FAQ

  • What is the default access modifier if I don't specify one?

    The default access modifier for class members in C# is private.
  • Can a private member be accessed from a derived class?

    No, private members are not accessible from derived classes. You need to use protected if you want derived classes to access a member.
  • What is the difference between internal and protected internal?

    internal members are accessible within the same assembly. protected internal members are accessible within the same assembly or from derived classes, even if they are in a different assembly.