C# access modifier
last modified October 18, 2026
In this article we show how to control the visibility of types and their members in C#.
Access modifiers set the accessibility of types and of their members, such as fields, properties, and methods. They let a type hide its internal details and expose only a well-defined interface. This protects data against accidental modifications and makes programs more robust; it is the basis of encapsulation.
C# has four access modifiers: public, protected,
internal, and private. They can be combined into
protected internal and private protected, which gives
six accessibility levels:
public– accessible from any codeprotected– accessible within the class and in classes derived from itinternal– accessible from any code in the same assembly (the same project, compiled into one exe or DLL)protected internal– accessible from the same assembly, or from derived classes in other assembliesprivate protected– accessible from derived classes, but only in the same assemblyprivate– accessible only within the containing type
In addition, C# 11 added the file modifier, which limits a type to
the source file in which it is declared.
| Modifier | Class itself | Same assembly | Other assembly | ||
|---|---|---|---|---|---|
| Derived | Other | Derived | Other | ||
public | yes | yes | yes | yes | yes |
protected internal | yes | yes | yes | yes | no |
protected | yes | yes | no | yes | no |
internal | yes | yes | yes | no | no |
private protected | yes | yes | no | no | no |
private | yes | no | no | no | no |
The table summarizes the C# access modifiers, from the least to the most restrictive. Derived means code in a class derived from the class, Other means code in any other class.
C# default accessibility
If we do not specify an access modifier, the most restrictive sensible default
is used. Members of classes and structs are private by default.
Top-level types, which are not nested in another type, are
internal by default; they can only be public or
internal (or file). Interface members are
public by default.
var c = new Counter();
c.Increment();
c.Increment();
Console.WriteLine(c.Value);
// Console.WriteLine(c.count); error CS0122
class Counter
{
int count;
public int Value => count;
public void Increment() => count++;
}
The count field has no access modifier, so it is private. The
Counter class itself is internal.
// Console.WriteLine(c.count); error CS0122
Accessing the private field from outside the class would cause the
CS0122 error: the field is inaccessible due to its protection
level.
$ dotnet run 2
C# public and private
In the following example, we use the public and private access modifiers.
var p = new Person("Jane");
p.SetAge(17);
Console.WriteLine($"{p.Name} is {p.GetAge()} years old");
p.SetAge(-5);
class Person(string name)
{
public string Name = name;
private int age;
public int GetAge()
{
return age;
}
public void SetAge(int age)
{
if (age < 0)
{
Console.WriteLine("Age cannot be negative");
return;
}
this.age = age;
}
}
In the above program, we have two member fields. One is declared public, the other private.
public string Name = name; private int age;
The Name field is public; any code can read and change it. The
age field is private; it can be used only inside the
Person class.
public void SetAge(int age)
{
if (age < 0)
{
Console.WriteLine("Age cannot be negative");
return;
}
this.age = age;
}
If a member field is private, the only way to access it from the
outside is through the public members of the class. The SetAge
method checks the value before it changes the field, so the age can never be
negative. This is the main benefit of hiding data.
var p = new Person("Jane");
p.SetAge(17);
We create a new instance of the Person class and set the age
through the public method.
$ dotnet run Jane is 17 years old Age cannot be negative
Public fields are shown here for simplicity. In real code, data is exposed through properties.
C# accessor accessibility
The get and set accessors of a property can have
different accessibility. A common pattern is a property that everyone can read
but only the class can change.
var acc = new Account();
acc.Deposit(100);
acc.Deposit(50);
Console.WriteLine($"Balance: {acc.Balance}");
Console.WriteLine($"Transactions: {acc.Transactions}");
// acc.Balance = 1000; error CS0272
class Account
{
public decimal Balance { get; private set; }
public int Transactions { get; private set; }
public void Deposit(decimal amount)
{
Balance += amount;
Transactions++;
}
}
The balance of the account can be changed only through deposits.
public decimal Balance { get; private set; }
The property is public, but its setter is private. The accessor modifier must be more restrictive than the property itself.
// acc.Balance = 1000; error CS0272
Assigning to the property from outside the class would cause the
CS0272 error, because the set accessor is inaccessible.
$ dotnet run Balance: 150 Transactions: 2
C# private members and inheritance
A derived class inherits all members of its base class, including the private ones; they are part of every derived object. However, private members are not accessible in the derived class. Public and protected members are.
var derived = new Derived();
derived.Info();
class Base
{
public string name = "Base";
protected int id = 5323;
private bool isDefined = true;
public bool IsDefined() => isDefined;
}
class Derived : Base
{
public void Info()
{
Console.WriteLine("This is Derived class");
Console.WriteLine(name);
Console.WriteLine(id);
// Console.WriteLine(isDefined); error CS0122
Console.WriteLine(IsDefined());
}
}
In the preceding program, we have a Derived class which inherits
from the Base class. The Base class has three member
fields, all with different access modifiers.
class Derived : Base
The class Derived inherits from the Base class. The
base class is specified after a colon.
Console.WriteLine(name); Console.WriteLine(id); // Console.WriteLine(isDefined); error CS0122 Console.WriteLine(IsDefined());
The public and the protected members can be accessed
in the Derived class. The private field cannot be
accessed directly; if we uncommented the line, the code would not compile. The
derived class can still use the field indirectly, through the public
IsDefined method of the base class.
$ dotnet run This is Derived class Base 5323 True
C# access across assemblies
The internal, protected internal, and
private protected modifiers depend on assemblies, so we need two
projects to see them in action. We create a class library and a console
application that uses it.
$ mkdir Access $ cd Access $ dotnet new sln $ dotnet new classlib -o Shapes $ dotnet new console -o App $ dotnet sln add Shapes/Shapes.csproj App/App.csproj $ dotnet add App reference Shapes/Shapes.csproj
The Shapes library and the App application are
compiled into two separate assemblies. We delete the generated
Class1.cs file.
namespace Shapes;
public class Shape
{
public string PublicName = "public";
protected string ProtectedName = "protected";
internal string InternalName = "internal";
protected internal string ProtectedInternalName = "protected internal";
private protected string PrivateProtectedName = "private protected";
private string privateName = "private";
public string Describe() => $"Shape with a {privateName} field";
}
The Shape class has a field for each accessibility level.
namespace Shapes;
public class Square : Shape
{
public void Show()
{
Console.WriteLine("Square (derived, same assembly):");
Console.WriteLine($" {PublicName}");
Console.WriteLine($" {ProtectedName}");
Console.WriteLine($" {InternalName}");
Console.WriteLine($" {ProtectedInternalName}");
Console.WriteLine($" {PrivateProtectedName}");
}
}
The Square class derives from Shape in the same
assembly. It can access all fields except the private one.
using Shapes;
var shape = new Shape();
Console.WriteLine("Program (not derived, other assembly):");
Console.WriteLine($" {shape.PublicName}");
// Console.WriteLine(shape.InternalName); error CS1061
// Console.WriteLine(shape.ProtectedInternalName); error CS0122
Console.WriteLine($" {shape.Describe()}");
new Square().Show();
new Circle().Show();
class Circle : Shape
{
public void Show()
{
Console.WriteLine("Circle (derived, other assembly):");
Console.WriteLine($" {PublicName}");
Console.WriteLine($" {ProtectedName}");
Console.WriteLine($" {ProtectedInternalName}");
// Console.WriteLine(InternalName); error CS0103
// Console.WriteLine(PrivateProtectedName); error CS0103
}
}
In the application, we use the Shape class directly and through
the derived Circle class.
Console.WriteLine($" {shape.PublicName}");
// Console.WriteLine(shape.InternalName); error CS1061
// Console.WriteLine(shape.ProtectedInternalName); error CS0122
Code in another assembly that does not derive from Shape can use
only the public members. Internal members are not visible to other assemblies
at all, so the compiler reports that the class does not contain the member.
Console.WriteLine($" {ProtectedName}");
Console.WriteLine($" {ProtectedInternalName}");
// Console.WriteLine(InternalName); error CS0103
// Console.WriteLine(PrivateProtectedName); error CS0103
A derived class in another assembly can access the protected and
protected internal members, but not the internal and
private protected ones.
$ dotnet run --project App Program (not derived, other assembly): public Shape with a private field Square (derived, same assembly): public protected internal protected internal private protected Circle (derived, other assembly): public protected protected internal
The output matches the table at the beginning of the article. An assembly can
share its internal members with chosen other assemblies, typically with its
test project, using the InternalsVisibleTo attribute.
C# file access modifier
The file modifier, added in C# 11, restricts a type to the source
file in which it is declared. It is useful for helper types that should not be
visible anywhere else, and source generators use it to avoid name conflicts.
namespace FileEx;
public static class Report
{
public static string Create(string title) => Formatter.Underline(title);
}
file static class Formatter
{
public static string Underline(string text) =>
$"{text}\n{new string('-', text.Length)}";
}
The Formatter class is visible only in the Report.cs
file. The public Report class uses it internally.
using FileEx;
Console.WriteLine(Report.Create("Monthly report"));
// Console.WriteLine(Formatter.Underline("test")); error CS0117
class Formatter
{
}
In Program.cs, we declare another class named
Formatter. There is no conflict, because the file-local class is
invisible here. The Formatter name in this file refers to our
empty class, so calling Underline would cause the
CS0117 error.
$ dotnet run Monthly report --------------
Source
Access Modifiers - programming guide
In this article we worked with C# access modifiers.
Author
List all C# tutorials.