DEV Community

Cover image for C# Basics - What are the types of access modifiers in C#?
Jarryd
Jarryd

Posted on

C# Basics - What are the types of access modifiers in C#?

What Are the Types of Access Modifiers in C#?

Often, as junior C# developers, we write code all day that works, but we see words like private, public, internal, or protected on our classes, structs, and records. These are called access modifiers — but do we really know what they mean and when to use them?

Hopefully, this blog post will help you understand C# access modifiers better.


1. Public

Public members can be accessed by any type from any assembly.

Code Example – Same Assembly:

namespace AccessModifiers;

public class TestClass
{
    public string PublicField = "public";
}

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        Console.ReadKey();
    }
}
Enter fullscreen mode Exit fullscreen mode

Code Example – Another Assembly:

using AccessModifiers;

namespace AccessModifiersAnotherAssembly;

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        Console.ReadKey();
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Internal

Internal members can be accessed by any other type within the same assembly in which they are defined. They cannot be accessed from outside that assembly.

Code Example:

namespace AccessModifiers;

public class TestClass
{
    public string PublicField = "public";
    internal string InternalField = "internal";
    protected string ProtectedField = "protected";
}

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        Console.WriteLine($"Accessing internal: {testClassObject.InternalField}");
        Console.ReadKey();
    }
}
Enter fullscreen mode Exit fullscreen mode

Code snippet – Another Assembly (unable to access the internal field):


3. Protected

Protected members can only be used by types that derive from the base class — in this case, TestClass.

They can also be accessed from another assembly if the derived class exists in that assembly.

Code Example:

using AccessModifiers;

namespace AccessModifiersAnotherAssembly;

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        // Console.WriteLine($"Accessing protected: {testClassObject.ProtectedField}"); // Not allowed
        Console.ReadKey();
    }
}

public class ChildOfTestClassAnotherAssembly : TestClass
{
    public ChildOfTestClassAnotherAssembly()
    {
        Console.WriteLine(base.ProtectedField); // Allowed - derived class
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Private

The private modifier restricts access to members within the same class only. It cannot be accessed from any other class.

Code Example:

public class TestClass
{
    public string PublicField = "public";
    internal string InternalField = "internal";
    protected string ProtectedField = "protected";
    protected internal string ProtectedInternalField = "protected internal";
    private protected string PrivateProtectedField = "private protected";
    private string PrivateField = "private";
}
Enter fullscreen mode Exit fullscreen mode

5. Protected Internal

Within the same assembly, protected internal behaves like internal, meaning it can be accessed from any class in that assembly.

In another assembly, it behaves like protected, meaning it’s only accessible from a derived class.

Code Example – Assembly 1:

namespace AccessModifiers;

public class TestClass
{
    public string PublicField = "public";
    internal string InternalField = "internal";
    protected string ProtectedField = "protected";
    protected internal string ProtectedInternalField = "protected internal";
}

public class ChildOfTestClass : TestClass
{
    public ChildOfTestClass()
    {
        Console.WriteLine(base.ProtectedField);
        Console.WriteLine(base.ProtectedInternalField);
    }
}

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        var testClassObject2 = new ChildOfTestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        Console.WriteLine($"Accessing internal: {testClassObject.InternalField}");
        Console.WriteLine($"Accessing protected internal: {testClassObject.ProtectedInternalField}");
        Console.ReadKey();
    }
}
Enter fullscreen mode Exit fullscreen mode

Code Example – Assembly 2:

using AccessModifiers;

namespace AccessModifiersAnotherAssembly;

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        Console.ReadKey();
    }
}

public class ChildOfTestClassAnotherAssembly : TestClass
{
    public ChildOfTestClassAnotherAssembly()
    {
        Console.WriteLine(base.ProtectedField);
        Console.WriteLine(base.ProtectedInternalField); // Works only because of inheritance
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Private Protected

Within the same assembly, private protected behaves like protected — meaning it can be accessed from derived classes within the same assembly only.

Outside of the assembly, it is not accessible at all.

Code Example:

namespace AccessModifiers;

public class TestClass
{
    public string PublicField = "public";
    internal string InternalField = "internal";
    protected string ProtectedField = "protected";
    protected internal string ProtectedInternalField = "protected internal";
    private protected string PrivateProtectedField = "private protected";
}

public class ChildOfTestClass : TestClass
{
    public ChildOfTestClass()
    {
        Console.WriteLine(base.ProtectedField);
        Console.WriteLine(base.ProtectedInternalField);
        Console.WriteLine(base.PrivateProtectedField); // Works - same assembly
    }
}

public class Program
{
    static void Main(string[] args)
    {
        var testClassObject = new TestClass();
        var testClassObject2 = new ChildOfTestClass();
        Console.WriteLine($"Accessing public: {testClassObject.PublicField}");
        Console.WriteLine($"Accessing internal: {testClassObject.InternalField}");
        Console.WriteLine($"Accessing protected internal: {testClassObject.ProtectedInternalField}");
        // Console.WriteLine(testClassObject.PrivateProtectedField); // Not accessible
        Console.ReadKey();
    }
}
Enter fullscreen mode Exit fullscreen mode

All the examples above use classes, but access modifiers work the same way for classes, structs, and records.

However, not all access modifiers are supported by structs, since structs do not support inheritance.


Top comments (0)