Question
C# Access Modifiers and static: public, private, protected, and Defaults
Question
In C#, what are the differences between the public, private, and protected access modifiers, and what happens when no access modifier is written?
I have mostly used public throughout college and want to understand when each access level should be used. Also, how does the static keyword differ from leaving it off a member?
Short Answer
You will learn how C# access modifiers control who can use a type or member, what default accessibility means, and why static is a separate concept from accessibility. You will also see when to use instance members versus class-level static members.
Concept
Access modifiers define accessibility: the parts of a program that are allowed to use a type, field, method, property, or constructor.
publicmeans accessible from any code that can access the containing type or assembly.privatemeans accessible only inside the type that declares it.protectedmeans accessible inside the declaring type and inside types derived from it.- Omitting an access modifier uses a C# default that depends on where the declaration appears.
For members declared directly in a class or struct, the default is private:
class Account
{
string ownerName; // private by default
}
For a top-level type declared directly in a namespace, the default is internal, meaning it is available anywhere in the same assembly (usually the same project output), but not from another assembly:
class ReportGenerator // internal by default
{
}
static is not an access modifier. It answers a different question:
- Access modifiers answer: Who may use this?
staticanswers: Does this belong to the class itself, or to each object created from the class?
A member without static is an instance member. Each object has its own instance data and uses instance methods through an object reference. A static member belongs once to the type itself.
Mental Model
Think of a class as an apartment building.
publicis the building lobby: anyone allowed into the building can use it.privateis an apartment's locked safe: only that apartment can access it.protectedis a private family space: the apartment itself and apartments belonging to its inherited family can access it.- No modifier means C# applies the building's default rule for that location.
Now think of static as the difference between a shared building noticeboard and each apartment's own mailbox:
- A
staticfield is the one shared noticeboard for the entire building. - An instance field is a separate mailbox for every apartment.
Accessibility and static can be combined. For example, a field can be private static: private to the class but shared by every instance.
Syntax and Examples
A class can expose a small public API while keeping implementation details private.
public class BankAccount
{
private decimal balance;
public string Owner { get; }
public BankAccount(string owner, decimal openingBalance)
{
Owner = owner;
balance = openingBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount));
}
balance += amount;
}
public decimal GetBalance()
{
return balance;
}
}
BankAccount is public, so another project can use the type if it references the assembly. Deposit and GetBalance are public because they are intended operations for callers. balance is private, so callers cannot directly set it to an invalid value.
var account = new BankAccount("Mina", 100m);
account.Deposit(25m);
Console.WriteLine(account.GetBalance()); // 125
// account.balance = -500m; // Does not compile: balance is private.
protected is useful when a base class provides data or behavior specifically for derived classes:
public class Employee
{
protected decimal BaseSalary { get; }
public Employee(decimal baseSalary)
{
BaseSalary = baseSalary;
}
}
public class SalesEmployee : Employee
{
public SalesEmployee(decimal baseSalary) : base(baseSalary)
{
}
public decimal CalculatePay(decimal commission)
{
return BaseSalary + commission;
}
}
SalesEmployee can read BaseSalary because it derives from Employee. Regular outside code cannot access BaseSalary directly.
Static and instance members can appear in the same class:
public class VisitorCounter
{
public static int TotalVisitors { get; private set; }
public string Name { get; }
public VisitorCounter(string name)
{
Name = name;
TotalVisitors++;
}
}
var first = new VisitorCounter("Ava");
var second = new VisitorCounter("Noah");
Console.WriteLine(first.Name); // Ava
Console.WriteLine(VisitorCounter.TotalVisitors); // 2
Name belongs to each object. TotalVisitors is one value shared by the VisitorCounter type.
Step by Step Execution
Consider this code:
public class ScoreTracker
{
private int score;
public static int CreatedCount { get; private set; }
public ScoreTracker()
{
CreatedCount++;
}
public void AddPoints(int points)
{
score += points;
}
public int GetScore()
{
return score;
}
}
var playerOne = new ScoreTracker();
playerOne.AddPoints(10);
var playerTwo = new ScoreTracker();
playerTwo.AddPoints(5);
Console.WriteLine(playerOne.GetScore()); // 10
Console.WriteLine(playerTwo.GetScore()); // 5
Console.WriteLine(ScoreTracker.CreatedCount); // 2
Execution trace:
playerOneis created. Its instance fieldscorestarts at0.- The constructor increments the shared static
CreatedCountfrom0to1. playerOne.AddPoints(10)changes onlyplayerOne's privatescoreto10.playerTwois created. It gets a separatescorefield, also starting at0.- The constructor increments the single shared
CreatedCountfrom1to2. playerTwo.AddPoints(5)changes onlyplayerTwo's score to5.- The two objects have different instance scores, while both contribute to the same static count.
Outside the class, code can call GetScore because it is public. It cannot read or write score directly because it is private.
Real World Use Cases
- Public API methods: A service exposes
public Task<User> GetUserAsync(...)so controllers and other consumers can request user data. - Private implementation details: A class stores a private database connection helper or calculation method so callers cannot depend on internal implementation choices.
- Protected extension points: A framework base class gives derived classes a protected method such as
ValidateRequest()or a protected property needed to customize behavior. - Internal application code: In a multi-project solution,
internaltypes can be shared inside one assembly without becoming part of a library's public API. - Static utility methods:
Math.Sqrt,string.IsNullOrWhiteSpace, and helper classes often use static methods when no object-specific state is needed. - Static shared state: Application-wide configuration, cached read-only data, counters, or a singleton-like shared resource may be static. Shared mutable state should be used carefully because it can make tests and concurrent code harder to manage.
Real Codebase Usage
Developers usually keep fields private and provide intentional public methods or properties. This protects class invariants: rules that must always remain true.
A common validation pattern is to expose a public method while keeping mutation private:
public class UserProfile
{
public string DisplayName { get; private set; }
public UserProfile(string displayName)
{
DisplayName = ValidateName(displayName);
}
public void ChangeDisplayName(string newName)
{
DisplayName = ValidateName(newName);
}
private static string ValidateName(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("A display name is required.", nameof(value));
}
return value.Trim();
}
}
Here:
DisplayNameis publicly readable but can only be changed insideUserProfilebecause its setter is private.ValidateNameis private because it is an implementation detail.ValidateNameis static because it does not need data from a particularUserProfileobject.
Use protected sparingly in reusable base classes. A protected member becomes part of the contract that derived classes may rely on, making future changes harder. Often, a private member plus a carefully designed protected method is safer than exposing a protected field.
public abstract class Document
{
public void Save()
{
Validate();
WriteFile();
}
protected virtual void Validate()
{
}
private void WriteFile()
{
// Shared file-writing implementation.
}
}
This lets derived classes customize validation without giving them direct access to all internal state.
Common Mistakes
Making every field public
This allows callers to put an object into an invalid state.
public class Temperature
{
public double Celsius;
}
var temperature = new Temperature();
temperature.Celsius = -1000; // Possibly invalid for the application's rules.
Prefer private fields and validate updates through a method or property.
public class Temperature
{
private double celsius;
public double Celsius
{
get => celsius;
set
{
if (value < -273.15)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
celsius = value;
}
}
}
Assuming no modifier always means private
It depends on the declaration location.
class Helper // internal because it is a top-level type
{
void Run() // private because it is a class member
{
}
}
Remember: top-level types default to internal; class and struct members default to private.
Treating protected as public to every object
protected is for the declaring class and derived classes. It does not mean arbitrary code can access the member.
public class Parent
{
protected int Value = 10;
}
var parent = new Parent();
// Console.WriteLine(parent.Value); // Does not compile.
Calling an instance method using the type name
public class Greeter
{
public void SayHello()
{
Console.WriteLine("Hello");
}
}
// Greeter.SayHello(); // Does not compile: SayHello is not static.
Create an object first, or make the method static only if it does not depend on object state.
var greeter = new Greeter();
greeter.SayHello();
Using static for per-object data
public class Player
{
public static int Score;
}
Every Player would share one score. Remove static if every player needs an independent score.
Exposing protected fields
A protected field lets derived classes change internal state without validation. Prefer a private field and protected methods or properties when inheritance genuinely needs an extension point.
Comparisons
| Feature | What it controls | Who can access or use it | Typical use |
|---|---|---|---|
public | Accessibility | Any permitted caller | A type's intended API |
private | Accessibility | Only the declaring type | Fields and internal helper methods |
protected | Accessibility | Declaring type and derived types | Carefully designed inheritance hooks |
internal | Accessibility | Code in the same assembly | Project or library implementation types |
| No modifier on class member | Default accessibility | Private for class/struct members | Omitted modifier, though explicit private can improve clarity |
| No modifier on top-level type | Default accessibility | Internal within the assembly | Non-public top-level implementation type |
static | Ownership/lifetime | Accessed through the type | Shared state and stateless utilities |
No static | Ownership/lifetime | Accessed through an object instance | Data and behavior unique to each object |
static can be used with an access modifier:
private static int nextId;
public static string ApplicationName { get; set; } = "Portal";
The first member is shared but only usable inside its class. The second is shared and publicly available.
Cheat Sheet
public class Product // Available to other assemblies
{
private decimal price; // Only this Product class
protected string Code { get; set; } // Product and derived classes
public string Name { get; } // Callers can read it
internal void Refresh() { } // Same assembly only
public static int Count { get; private set; } // One value for Product
public void ApplyDiscount(decimal percent) { } // Called on a Product object
}
- Use
publicfor a deliberate API. - Use
privateby default for fields and implementation helpers. - Use
protectedonly when derived classes need an intentional extension point. - A top-level class without a modifier is
internal. - A class or struct member without a modifier is
private. staticmeans one member belongs to the type, not one per object.- Non-static members need an instance:
var product = new Product();
product.ApplyDiscount(10m);
- Static members are normally accessed by type name:
Console.WriteLine(Product.Count);
private staticis common for shared implementation data.- Avoid unnecessary public fields and mutable static state.
FAQ
What is the default access modifier for a C# class?
A top-level class declared in a namespace defaults to internal. A nested class defaults to private.
What is the default access modifier for C# methods and fields?
Methods, fields, properties, and constructors declared directly in a class or struct default to private when no modifier is written.
Should I use public or private by default in C#?
Use the least access necessary. Fields are normally private. Make a member public only when other code truly needs it as part of the class's intended API.
Can code outside a class access a protected member?
No. A protected member is accessible within the declaring class and from derived classes, subject to C#'s inheritance access rules.
Is static an access modifier in C#?
No. static controls whether a member belongs to the type or to each object instance. It is independent of public, private, and protected.
When should a C# method be static?
Make it static when it does not need instance fields, instance properties, or other instance behavior. Utility and validation methods are common examples.
Can a static member be private or public?
Yes. For example, private static int counter; is shared within the class, while public static int Counter { get; } can be accessed through the type.
Why should fields usually be private?
Private fields prevent outside code from changing internal state directly. Public methods and properties can validate changes and preserve the object's rules.
Mini Project
Description
Build a small library tracker that keeps each book's availability as instance state while also keeping a shared count of all books created. The project demonstrates private fields, public behavior, a protected extension point, and a static shared value.
Goal
Create book objects that can be borrowed safely and report the total number of books created.
Requirements
- Create a public
LibraryBookclass with a title supplied by its constructor. - Keep the borrowed status inaccessible for direct changes from outside the class.
- Provide public methods to borrow and return a book.
- Track the total number of created books with a static public read-only property.
- Create a derived
ReferenceBookclass that uses protected behavior to prevent borrowing. - Demonstrate the behavior by creating and using both book types.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.