C# Access Modifiers

Controlling Accessibility Level of Types and Members in C#

Jiyan Epözdemir
.Net Programming
9 min readJul 18, 2024

--

All types and type members in C# have an accessibility level. Whether they can be used from other code in current assembly or other assemblies depends on the accessibility level. In C#, there are various keywords called ‘Access Modifiers’ that are used to define that accessibility level. They used to specify the scope of accessibility of a member or a type.

In this blog post, we’ll explain each access modifiers available in C#, their purposes, and help clarify how different access modifiers control where and how members of a class can be accessed, ensuring your code is organized and secure.

Why Access Modifiers?

Access modifiers are used in C# to control who can see and use different parts of your code. They assist you to organize and protect your code, improving its maintainability.

Organize Your Code : “Show what’s meant to be used”

By marking some methods and variables as public, you make it clear which parts of your code are meant to be used by other parts of your program or other programmers.

Protect Your Code : “Keep sensitive parts private”

Some parts of your code should only be used within the class itself. By making them private, you ensure no other part of your program can access or change them.

Encapsulation : “Hide the details”

You can hide the complex parts of your code inside the class, exposing only what’s necessary. This makes your code easier to use and reduces the chance of errors.

Improve Maintenance : “Easier to understand”

When other programmers see your code, access modifiers help them understand how it’s supposed to be used. They can see at a glance which parts are safe to use and which parts should be left alone.

What are Access Modifiers?

As I mentioned above, access modifiers determine the level of accessibility of a class member (method, variable, etc.) from other parts of the program. They are not allowed on namespaces. Namespaces have no access restrictions.

In C#, there are 5 access modifiers:

  • public
  • private
  • protected
  • internal
  • file ( only in C# 11 and later )

Only one access modifier is allowed for a member or type, except when you use the protected internal or private protected combinations.

The following 7 accessibility levels can be specified using the access modifiers:

  • public
  • private
  • protected
  • internal
  • protected internal
  • private protected
  • file

Now, let’s give a brief overview of each accessibility level with a few examples.

“public” Accessibility Level

“Share with the World”

Members marked as public are accessible from any other code in the same assembly or another assembly that references it. This is the least restrictive access level.

public class MyClass
{
public int PublicField;

public void PublicMethod()
{
PublicField = 20;
}
}

PublicFieldand PublicMethodcan be accessed from any part of your program, even from other classes or assemblies. This means that if another class creates an instance of MyClass, it can directly access PublicField and call PublicMethod.

“private” Accessibility Level

“Only for the Class”

Members marked as private are accessible only within the same class or struct. They are not accessible from outside the containing class.

public class MyClass
{
private int privateField;

private void PrivateMethod()
{
privateField = 10;
}
}

privateFieldand PrivateMethodcan only be accessed within the MyClass itself. This means you can't access these members from any other class or even a derived class. Only code inside MyClass can read or modify privateField or call PrivateMethod.

“protected” Accessibility Level

“Share with Derived Classes”

Protected members are accessible within the same class or from derived classes. They are not accessible from outside the class or struct.

public class MyClass
{
protected int ProtectedField;

protected void ProtectedMethod()
{
ProtectedField = 30;
}
}

public class DerivedClass : MyClass
{
public void AccessProtectedMember()
{
// Derived class can access protected members
ProtectedMethod();
ProtectedField = 40;
}
}

ProtectedFieldand ProtectedMethodcan be accessed within MyClass and any class that inherits from MyClass (like DerivedClass). This is useful for allowing subclasses to use certain members of their parent class.

“internal” Accessibility Level

“Inside the Assembly”

Internal members are accessible only within the same assembly. They cannot be accessed from outside the assembly, even from derived types.

public class MyClass
{
internal int InternalField;

internal void InternalMethod()
{
InternalField = 50;
}
}

InternalFieldand InternalMethodcan be accessed by any code within the same assembly. An assembly is usually a single compiled executable or DLL. This means you can’t access these members from another assembly, even if you use the same namespace.

“protected internal” Accessibility Level

“Sharing Among Friends”

Protected internal members are accessible from within the same assembly or from derived classes in another assembly. It’s a combination of protected and internal access modifiers.

public class MyClass
{
protected internal int ProtectedInternalField;

protected internal void ProtectedInternalMethod()
{
// Protected internal method can be accessed within the same assembly or from derived classes in another assembly
ProtectedInternalField = 60;
}
}

Note that, ProtectedInternalFieldand ProtectedInternalMethod can be accessed by any code within the same assembly, just like internal members. Additionally, they can be accessed by derived classes, even if those derived classes are in a different assembly. This provides a flexible access level for members that need to be accessible in both scenarios.

“private protected” Accessibility Level

“Safe Within and By Kin”

The private protected access modifier is one of the access levels available in C#. It combines aspects of both private and protected modifiers and provides a specific level of access control.

Members declared with the private protected modifier are accessible within the same class or in derived classes, but only if those derived classes are in the same assembly.

public class BaseClass
{
private protected int PrivateProtectedField;

private protected void PrivateProtectedMethod()
{
Console.WriteLine("Private Protected Method in BaseClass");
}
}

public class DerivedClass : BaseClass
{
public void AccessPrivateProtectedMember()
{
// Can access PrivateProtectedField and PrivateProtectedMethod
PrivateProtectedField = 10;
PrivateProtectedMethod();
}
}

// This class is in the same assembly but not derived from BaseClass
public class AnotherClass
{
public void TryAccessing()
{
var baseObject = new BaseClass();

// Can't access PrivateProtectedField or PrivateProtectedMethod here
// baseObject.PrivateProtectedField = 20; // Error
// baseObject.PrivateProtectedMethod(); // Error
}
}

In the example, BaseClasscontains a field (PrivateProtectedField) and a method (PrivateProtectedMethod) marked as private protected. These members are accessible only within BaseClassor derived classes such as DerivedClassin the same assembly. However, AnotherClasscan not access even though it is in the same assembly because it does not derive from BaseClass.

This access level is useful when you want to allow derived classes within the same assembly to access certain members but prevent access from other classes.

“file” Accessibility Level

Secrets Kept Within Single file”

In C# 11 and later, a new access modifier called file has been introduced. This modifier allows you to limit the visibility of types to the file in which they are declared. This is particularly useful for hiding implementation details that are not meant to be used outside of a specific source file.

The file access modifier restricts the visibility of a type to the file it is declared in. It is allowed only on top-level (non-nested) type declarations.

file class FilePrivateClass
{
public void FilePrivateMethod()
{
Console.WriteLine("This method is in a file-private class.");
}
}

public class PublicClass
{
public void PublicMethod()
{
// Can access FilePrivateClass because it's in the same file
FilePrivateClass filePrivate = new FilePrivateClass();
filePrivate.FilePrivateMethod();
}
}

Note that, FilePrivateClassdeclared with the file modifier. That means it can only be accessed within the same file where it is defined. This class and its members are completely hidden from other files in the same project. PublicClass can create an instance of FilePrivateClass and call its methods because it within the same file. If you try to access FilePrivateClass from another file, it will not be visible, and you will get a compilation error.

The file access modifier is useful in scenarios where you want to keep certain implementation details private to a specific file. This can help in:

  • Reducing the scope of types and preventing their misuse in other parts of the codebase. It means; you have less need for nested types when you want to keep certain types private to a single file.
  • Organizing code by keeping helper classes and methods within the same file as the main class they support.

Defaults

In C#, when you don’t specify an access modifier, the default access levels are applied based on the type of member.

Here are the default access levels for different member types:

  • Top-level (non-nested) classes and structs : default to internal.
  • Members of a Class or Struct (Fields, Methods, Properties, etc.) : default to private.
  • Nested Types (Classes, Structs, Enums, etc.) : default to private.
  • Interfaces : just like classes and structs, interfaces default to internal access. But interface members are public by default because the purpose of an interface is to enable other types to access a class or struct.
  • Enums: Finalizers can’t have accessibility modifiers. Members of an enum type are always public, and no access modifiers can be applied.
  • A delegate type declared directly in a namespace has internal access by default.
// Default access for top-level class is internal
class MyClass
{
// Default access for fields, methods, and properties is private
int myField;

void MyMethod()
{
// Method implementation...
}

// Default access for nested classes is private
class NestedClass
{
// Members of NestedClass...
}
}

// Default and only access for interfaces is public
interface IMyInterface
{
void MyMethod();
}

// Default and only access for enums is public
enum MyEnum
{
MyEnumMember
}

// Default access for delegates is internal
delegate int MyDelegate();

Internals To Visible

You can enable specific other assemblies to access your internal types by using the InternalsVisibleToAttribute. This is useful for unit testing, where you want to test internal members without making them public.

The InternalsVisibleTo attribute is added to the AssemblyInfo.cs file or directly within the assembly attributes in your code.

Let’s say you have two assemblies: MainAssembly and TestAssembly. You want the TestAssembly to have access to the internal members of MainAssembly.

In MainAssembly, open the AssemblyInfo.cs file (typically found under the Properties folder) and add the following lines:

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("TestAssembly")]

Since TestAssembly is now specified in the InternalsVisibleTo attribute, it can access the internal members of MainAssembly.

Some Key Points

  • Members of a class or struct (including nested classes and structs) can be declared with any of the six types of access level (except file).
  • Struct members can't be declared as protected, protected internal, or private protected because structs don't support inheritance.
  • Derived classes can’t have greater accessibility than their base types. You can’t declare a public class B that derives from an internal class A. If allowed, it would have the effect of making A public, because all protected or internal members of A are accessible from the derived class.
  • Not all access modifiers are applicable for all types or members in all contexts. In some cases, the accessibility of the containing type restricts the accessibility of its members.
  • When one declaration of a partial classclass or methoddoesn’t declare its accessibility, it has the accessibility of the other declaration. The compiler generates an error if multiple declarations for apartial classclass or method declare different accessibilities.
  • The record modifier on a type causes the compiler to synthesize extra members. The record modifier doesn't affect the default accessibility for either a record class or a record struct.
  • An interface member with private accessibility must have a default implementation.

Conclusion

To sum up, publicmembers can be accessed from anywhere, privatemembers are limited to the defining class, filemembers are private to a specific file, protectedmembers are accessible to derived classes, internalmembers are accessible within the same assembly, protected internalmembers are accessible within the same assembly or by derived classes, and private protectedmembers are accessible within the same class or in derived classes that in the same assembly.

Now you see, how different access modifiers control the accessibility of fields and methods within a class.

Here a few recommendations as last words:

Choose the most restrictive access modifier that still allows your code to function correctly. This reduces coupling between different parts of your code and enhances maintainability.

When designing classes for inheritance, consider using protected members to allow derived classes to access certain members while still maintaining encapsulation.

Encapsulate data members using private access modifiers and expose them through public properties or methods. This helps in maintaining the integrity of the data and controlling access to it.

When you have members that should only be accessible within the same assembly, use the internal access modifier. This prevents unintentional access from outside the assembly.

Sample codes used here are available in the following GitHub repository:

Thank you for reading!

If you found this post helpful, don’t forget to give it a clap and share it with others who might benefit from it.👏👏👏👏👏

--

--

Jiyan Epözdemir
.Net Programming

I am a multi-disciplined Software Architect and Computer Engineer, MSc. I enjoy building awesome softwares & applications.