C# Access Modifiers
Controlling visibility and access to class members
๐ What are Access Modifiers?
Access modifiers control the visibility and accessibility of classes, methods, and fields. They determine which parts of your code can access specific members, ensuring encapsulation and security.
class Example
{
public int publicField;
private int privateField;
protected int protectedField;
}
Types of Access Modifiers
public
Accessible from anywhere
public string Name;
public void Display() { }
private
Only within the same class
private int age;
private void Calculate() { }
protected
Class and derived classes
protected string id;
protected void Process() { }
internal
Within the same assembly
internal class Helper { }
internal void Update() { }
๐น Public Access Modifier
The `public` access modifier provides the least restrictive level of access for class members. A public member is accessible from anywhere: within the same class, from other classes in the same assembly, from derived classes, and even from other assemblies (projects) that reference it. Use `public` for members that form the official, stable API of your classโmethods and properties you intend for external consumers to use. For example, a `BankAccount` class would likely have a public `AccountNumber` property and public `Deposit()` and `Withdraw()` methods, as these are essential interactions for clients of the class.
class BankAccount
{
// Public field - accessible everywhere
public string AccountNumber;
// Public property
public string Owner { get; set; }
// Public method
public void DisplayInfo()
{
Console.WriteLine("Account: " + AccountNumber);
Console.WriteLine("Owner: " + Owner);
}
}
class Program
{
static void Main()
{
BankAccount account = new BankAccount();
// Can access public members
account.AccountNumber = "123456";
account.Owner = "John Doe";
account.DisplayInfo();
}
}
Output:
Account: 123456
Owner: John Doe
๐น Private Access Modifier
The `private` access modifier is the most restrictive, encapsulating members within the class they are declared in. Private members are only accessible from other methods inside the same class. They are completely hidden from outside, including derived classes. This is fundamental to encapsulation, allowing you to hide implementation details and internal state. For instance, a `Salary` field might be private, with access controlled through a public `GetSalary()` method or property that can include validation logic. Changing how a private field is stored (e.g., from an `int` to a `decimal`) doesn't break external code, promoting maintainability.
class Employee
{
// Private fields - only accessible within this class
private string password;
private decimal salary;
// Public property to access private field
public string Name { get; set; }
// Public method to set private field with validation
public void SetSalary(decimal amount)
{
if (amount > 0)
{
salary = amount;
Console.WriteLine("Salary set successfully");
}
else
{
Console.WriteLine("Invalid salary amount");
}
}
// Public method to get private field
public decimal GetSalary()
{
return salary;
}
// Private method - only used internally
private bool ValidatePassword(string pass)
{
return pass.Length >= 8;
}
}
class Program
{
static void Main()
{
Employee emp = new Employee();
emp.Name = "Alice";
// Can access public members
emp.SetSalary(50000);
Console.WriteLine("Salary: $" + emp.GetSalary());
// Cannot access private members
// emp.password = "test"; // ERROR!
// emp.salary = 60000; // ERROR!
}
}
Output:
Salary set successfully
Salary: $50000
๐น Protected Access Modifier
The `protected` access modifier offers a level of access between `private` and `public`, focused on inheritance. Protected members are accessible within the class they are declared in and within any derived (child) class. However, they are not accessible from unrelated classes outside the inheritance hierarchy. This allows a base class to share implementation details with its subclasses while still hiding them from the rest of the application. For example, a base `Vehicle` class might have a protected `engineType` field that derived `Car` and `Motorcycle` classes can use and modify, but which is hidden from a `Garage` class that merely uses vehicles.
// Base class
class Vehicle
{
// Protected field - accessible in derived classes
protected string engineType;
// Public field
public string Brand;
// Protected method
protected void StartEngine()
{
Console.WriteLine("Engine started: " + engineType);
}
}
// Derived class
class Car : Vehicle
{
public void SetupCar()
{
// Can access protected members from base class
engineType = "V6";
Brand = "Toyota";
Console.WriteLine("Car brand: " + Brand);
StartEngine(); // Can call protected method
}
}
class Program
{
static void Main()
{
Car myCar = new Car();
myCar.SetupCar();
// Can access public members
myCar.Brand = "Honda";
// Cannot access protected members
// myCar.engineType = "V8"; // ERROR!
// myCar.StartEngine(); // ERROR!
}
}
Output:
Car brand: Toyota
Engine started: V6
๐น Internal Access Modifier
The `internal` access modifier restricts member accessibility to files within the same assembly (or project). An internal member is accessible from any class in the same compiled DLL or EXE, but is invisible to code in other assemblies. This is ideal for creating helper classes, utility methods, or component interfaces that are meant for use only within your own project library and should not be exposed as part of the public API. For instance, a `DataAccess` assembly might have an internal `ConnectionHelper` class that manages database connections, keeping that complexity hidden from a separate `UI` assembly that only calls the public `DataManager` methods.
// Internal class - only accessible in same assembly
internal class DatabaseHelper
{
internal string ConnectionString;
internal void Connect()
{
Console.WriteLine("Connecting to: " + ConnectionString);
}
}
// Public class
public class DataManager
{
private DatabaseHelper dbHelper;
public DataManager()
{
// Can use internal class within same assembly
dbHelper = new DatabaseHelper();
dbHelper.ConnectionString = "Server=localhost";
}
public void Initialize()
{
dbHelper.Connect();
Console.WriteLine("DataManager initialized");
}
}
class Program
{
static void Main()
{
// Can use internal class in same assembly
DatabaseHelper helper = new DatabaseHelper();
helper.ConnectionString = "Server=myserver";
helper.Connect();
// Using public class
DataManager manager = new DataManager();
manager.Initialize();
}
}
Output:
Connecting to: Server=myserver
Connecting to: Server=localhost
DataManager initialized
๐น Protected Internal
The `protected internal` access modifier combines the rules of `protected` AND `internal` using a logical OR. A `protected internal` member is accessible from: 1) Any class within the same assembly (like `internal`), OR 2) Any derived class, even if it's in a different assembly (like `protected`). This is useful when you want to allow extensive access within your own codebase (assembly) but also permit inheritance and overriding by external libraries (other assemblies) that derive from your classes. It's a broader access level than either `protected` or `internal` alone.
class BaseClass
{
// Accessible in same assembly OR derived classes
protected internal string SharedData;
protected internal void SharedMethod()
{
Console.WriteLine("Shared method called");
}
}
class DerivedClass : BaseClass
{
public void AccessShared()
{
// Can access protected internal members
SharedData = "Data from derived class";
SharedMethod();
Console.WriteLine(SharedData);
}
}
class Program
{
static void Main()
{
BaseClass obj = new BaseClass();
// Can access in same assembly
obj.SharedData = "Data from same assembly";
obj.SharedMethod();
DerivedClass derived = new DerivedClass();
derived.AccessShared();
}
}
Output:
Shared method called
Shared method called
Data from derived class
๐น Access Modifier Comparison
Choosing the correct access modifier is vital for robust object-oriented design, balancing flexibility with encapsulation. `Public` is for your public API. `Private` hides internal state. `Protected` enables controlled sharing with subclasses. `Internal` creates project-wide utilities. `Protected Internal` offers the widest access for cross-assembly inheritance scenarios. The general principle is to use the most restrictive access level that still allows your code to function. Starting with `private` and escalating only when necessary (the "principle of least privilege") leads to more secure, maintainable, and loosely coupled code by minimizing unintended dependencies between different parts of your application.
class AccessExample
{
public string PublicField = "Everyone can see this";
private string PrivateField = "Only this class";
protected string ProtectedField = "This class and children";
internal string InternalField = "Same assembly only";
public void ShowAccess()
{
// All accessible within the same class
Console.WriteLine(PublicField);
Console.WriteLine(PrivateField);
Console.WriteLine(ProtectedField);
Console.WriteLine(InternalField);
}
}
class Program
{
static void Main()
{
AccessExample obj = new AccessExample();
// Only public and internal accessible here
Console.WriteLine(obj.PublicField);
Console.WriteLine(obj.InternalField);
// These would cause errors:
// Console.WriteLine(obj.PrivateField); // ERROR
// Console.WriteLine(obj.ProtectedField); // ERROR
obj.ShowAccess();
}
}
Output:
Everyone can see this
Same assembly only
Everyone can see this
Only this class
This class and children
Same assembly only
๐น Best Practices
When to Use Each Modifier:
- public: For APIs, interfaces, and members that need external access
- private: Default choice for fields and internal helper methods
- protected: For members that derived classes need to access
- internal: For helper classes used only within your project
- protected internal: Rarely used, for framework development
General Guidelines:
- Start with the most restrictive access level (private)
- Only increase visibility when necessary
- Use properties instead of public fields
- Keep implementation details private