C# Access Modifiers
Controlling Accessibility Level of Types and Members in C#
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
orprivate 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;
}
}
PublicField
and PublicMethod
can 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;
}
}
privateField
and PrivateMethod
can 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;
}
}
ProtectedField
and ProtectedMethod
can 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;
}
}
InternalField
and InternalMethod
can 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, ProtectedInternalField
and 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, BaseClass
contains a field (PrivateProtectedField
) and a method (PrivateProtectedMethod
) marked as private protected
. These members are accessible only within BaseClass
or derived classes such as DerivedClass
in the same assembly. However, AnotherClass
can 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, FilePrivateClass
declared 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 arepublic
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 alwayspublic
, and no access modifiers can be applied. - A
delegate
type declared directly in a namespace hasinternal
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
orstruct
(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
, orprivate 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 classA
. If allowed, it would have the effect of makingA
public, because allprotected
orinternal
members ofA
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 class
class ormethod
doesn’t declare its accessibility, it has the accessibility of the other declaration. The compiler generates an error if multiple declarations for apartial class
class ormethod
declare different accessibilities. - The
record
modifier on a type causes the compiler to synthesize extra members. Therecord
modifier doesn't affect the default accessibility for either arecord class
or arecord struct
. - An
interface
member withprivate
accessibility must have a default implementation.
Conclusion
To sum up, public
members can be accessed from anywhere, private
members are limited to the defining class, file
members are private to a specific file, protected
members are accessible to derived classes, internal
members are accessible within the same assembly, protected internal
members are accessible within the same assembly or by derived classes, and private protected
members 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.👏👏👏👏👏