In object oriented programming, encapsulation allows data and methods to have their access restricted.

C# Encapsulation Keywords

KeywordAccessibility
publicAnywhere
privateAccessible from within the same class
protectedAccessible from within the same class or child classes
internalAccessible from within the same project
protected internalAccessible within the same
assembly or in derived classes
(even in other assemblies)
private protectedAccessible only within the same class and derived classes within the same assembly

Examples

using System;
 
namespace EncapsulationDemo
{
    public class BankAccount
    {
        // 1. Private fields - hidden from direct external access
        private string _accountHolder;
        private decimal _balance;
 
        // 2. Encapsulated Property with validation
        public string AccountHolder
        {
            get { return _accountHolder; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentException("Account holder name cannot be empty.");
                }
                _accountHolder = value;
            }
        }
 
        // Read-only property (only has a getter)
        // This ensures the balance can only be modified via Deposit and Withdraw methods
        public decimal Balance
        {
            get { return _balance; }
        }
 
        // Constructor
        public BankAccount(string accountHolder, decimal initialDeposit)
        {
            AccountHolder = accountHolder; // Uses the property setter for validation
            
            if (initialDeposit < 0)
            {
                throw new ArgumentException("Initial deposit cannot be negative.");
            }
            _balance = initialDeposit;
        }
 
        // 3. Public methods to control how the private state is modified
        public void Deposit(decimal amount)
        {
            if (amount <= 0)
            {
                throw new ArgumentException("Deposit amount must be positive.");
            }
            _balance += amount;
            Console.WriteLine($"Successfully deposited ${amount}. Current balance: ${_balance}");
        }
 
        public void Withdraw(decimal amount)
        {
            if (amount <= 0)
            {
                throw new ArgumentException("Withdrawal amount must be positive.");
            }
            
            if (amount > _balance)
            {
                Console.WriteLine("Transaction Declined: Insufficient funds.");
                return;
            }
 
            _balance -= amount;
            Console.WriteLine($"Successfully withdrew ${amount}. Current balance: ${_balance}");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // Create a new bank account
                BankAccount account = new BankAccount("Zach", 500.00m);
                Console.WriteLine($"Account created for {account.AccountHolder} with balance: ${account.Balance}");
 
                // Try to deposit money (Allowed)
                account.Deposit(150.50m);
 
                // Try to withdraw money (Allowed)
                account.Withdraw(100.00m);
 
                // Try to perform an invalid withdrawal (Handled safely by business logic)
                account.Withdraw(1000.00m);
 
                // Compilation Errors if you try to bypass encapsulation:
                // account._balance = 1000000.00m; // Error: '_balance' is inaccessible due to its protection level
                // account.Balance = 1000000.00m;  // Error: Property 'Balance' cannot be assigned to because it is read-only
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}

Get and Set Methods in C#

Get and Set methods allow controlled access to private properties. This allows us to validate inputs and give outputs in specified formats.

C# has dedicated syntax for get and set methods, as you can see below.

public class User
{
    // The private backing field
    private string _username;
 
    // The public property containing the get/set accessors
    public string Username
    {
        get 
        { 
            return _username; 
        }
        set 
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException("Username cannot be empty.");
            }
            // 'value' is a built-in keyword representing the incoming data
            _username = value; 
        }
    }
}

C# also has ways of automatically generating get and set methods:

public class Product
{
    // The compiler automatically handles the get and set logic
    public string Name { get; set; }
    public decimal Price { get; set; }
}

and omitting the set; allows the creation of read-only properties:

public class Product
{
    // The compiler automatically handles the get and set logic
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Along with init only setting:

public class Product
{
    // The compiler automatically handles the get and set logic
    public string Name { get; set; }
    public decimal Price { get; set; }
}