solvera logo

Access Modifiers — C#

Access Modifiers — C#

Access modifiers are used to define the accessibility levels of classes, methods, variables, and other members. They play a key role in supporting encapsulation, one of the fundamental principles of object-oriented programming, by controlling which parts of a program can access certain code elements.

There are four main access modifiers: public, internal, protected, and private. Additionally, there are three lesser-known modifiers: protected internal, private protected, and file.

General Outline;

  1. Importance of access modifiers.
  2. Scenarios where we might want to access classes and members within a class.
  3. Where access modifiers can be defined and any restrictions on their use.
  4. An overview of all access modifiers.
  5. Default access modifiers if none are explicitly defined.

Why Are Access Modifiers Important?

Encapsulation: In object-oriented programming, it's essential to protect and hide a class's internal data and functions from the outside world. Access modifiers enable this encapsulation by specifying which data and methods are accessible from outside the class and which remain hidden within it.

Security: Access modifiers help prevent important or sensitive data from being accidentally altered or misused, thus maintaining data integrity through restricted access.

Maintenance Ease: By clearly defining which class or method can be used by another, access modifiers make the development process more predictable and manageable.

Cleaner Interfaces: Limiting a class's public interface to only essential methods and data makes it easier for other developers to understand and use it.

Error Management: By preventing the unintended calling of methods or modification of variables, access modifiers help prevent potential bugs in the program.

Overall, access modifiers aim to improve a program's security, flexibility, and quality.

 

Accessing Classes and Members
To understand access modifiers better, it's helpful to consider scenarios where we might want to access classes or members within a class:

  1. Access from within the same class.
  2. Access from different classes within the same assembly.
  3. Access from different classes in different assemblies.
  4. Access from within the same file.

An "assembly" generally refers to different projects within the same solution, such as console applications, class libraries, web applications, and web APIs. Based on this, we can say there are three main areas where access may be needed.

Note: For access modifiers that allow access from different assemblies, the relevant assembly must be referenced. Without referencing, even widely defined access modifiers like public won’t allow access to the target class or member. For example, to access a class in ProjectExampleA from ProjectExampleB, ProjectExampleA must be added as a reference in ProjectExampleB.

 

Definition Constraints
Classes can generally be marked with the public, internal, or file access modifiers. Other access modifiers have higher levels of protection, so they cannot be applied at the class level.

All remaining access modifiers can be applied to members within a class, with no restrictions on their use for class members.

 

Access Determinants

  1. Public

This access specifier indicates that the class or member within the class is accessible from anywhere. That is, it can be accessed from inside or outside the assembly (the executable code library of the application) where it is defined. It has no limitations and is the widest access specifier.

namespace ExampleProjectA
{
  public class Product
  {
      public string Code;
      public string Name;
  }
}
namespace ExampleProjectA
{
  public class Program
  {
      static void Main()
      {
          //Direct access to the public class.
          Product product = new();

          // Direct access to public members.
          product.Code = “PX-0”;
          product.Name = “Kuma”;
          Console.WriteLine($“Product : {product.Code} - {product.Name}”);
      }
  }
}
// Product : PX-0 - Kuma
namespace ExampleProjectB
{
  public class Program
  {
      static void Main()
      {
          //Public class access directly from a different assembly
          Product product = new();

          // Access to public members directly from a different assembly
          product.Code = “PX-0”;
          product.Name = “Kuma”;
          Console.WriteLine($“Product : {product.Code} - {product.Name}”);
      }
  }
}
// Product : PX-0 - Kuma
Product defined under ExampleProjectA can be accessed both from within ExampleProjectA and under ExampleProjectB. Access is not restricted in any way.

 

Internal
This specifies that the class or member within the class can only be accessed from within the assembly in which it resides. In other words, other code within the same project can access these members, but not from outside the project. This is often used to hide the internal details of the project from the outside.

If we set the Product class in our previous example to internal and try to access it from a different Assembly, it will only be accessible under the ExampleProjectA assembly and we will not be able to access it from anywhere in the ExampleProjectB assembly.

namespace ExampleProjectA
{
  internal class Product
  {
      public string Code;
      public string Name;
  }
}
namespace ExampleProjectB
{
  public class Program
  {
      static void Main()
      {
          // Since the Product object is defined as internal
          // The object cannot be accessed from this field.
          // Likewise, access could be provided if the Product was public
          // but if one of the members were internal
          // access to the relevant member would not be available.
      }
  }
}


Private
This specifies that the member within the class can only be accessed from within the class in which it is defined. That is, only members of that class can use members marked as private. This is used to ensure data confidentiality and in-class encapsulation.

Classes cannot be marked as private when they are defined. When we think about it logically, the code inside an inaccessible class is useless.

namespace ExampleProjectA
{
  public class Product
  {
      public string Code;
      public string Name;
      public int Quantity;
      public decimal Cost;

      private decimal GetTotalCost()
      {
        /* ... Codes ... */
        // This method cannot be accessed from anywhere except the Product class
        // Different members in the class are marked as private 
        // can access members.
      }
  }
}


Protected
This specifies that a member within a class can only be accessed from within the class in which it is defined or from subclasses derived from that class. This allows a class or member to be used by derived classes but protected from the outside world.

In general, it has the same logic as private. Exceptionally, access is also allowed from inherited classes. Classes cannot be marked as protected when they are defined.

namespace ExampleProjectA
{
  public class Product
  {
      public string Code;
      public string Name;
      public int Quantity;
      public decimal Cost;

      protected decimal GetTotalCost()
      {
        /* ... Codes ... */
        // This method cannot be accessed from anywhere except the Product class
        // Different members in the class are marked as protected 
        // can access members. If it is an inherited class, it can still access it.
      }
  }
}
namespace ExampleProjectA
{
  public class ElectronicProduct : Product
  { 
      public decimal CalculateTax()
      {  
        // GetTotalCost() is protected ce we inherit from Product
        We can access it for //.
        int totalCost = GetTotalCost();
        /* ... Other Codes ... */  
      }
  }
}

 

Protected Internal
This access specifier indicates that the member is accessible from within the class in which it is defined, from derived classes, and from other classes within the same assembly. That is, a member defined with the protected internal access specifier combines the access advantages of both protected and internal. This allows wide access both through derived classes and within the same assembly.Classes cannot be marked as protected internal when they are defined.

 

Private Protected
This access specifier indicates that the member within the class is accessible only from the class in which it is defined, or from classes derived from that class that are also within the assembly in which it is defined. private protected combines the properties of private and protected, but restricts access only to derived classes within the same assembly. This provides narrower access control and offers privacy for derived classes within a given assembly.

In the protected example, access was available from any inherited class, with private protected we restrict access from different assemblies. Classes cannot be marked as private protected when they are defined.

namespace ExampleProjectA
{
  file class Product
  {
      public int Code() => "PX-0";
  }

  public class Printer
  {
      public void PrintCode()
      {
          Console.WriteLine($"Product Code : {product.Code}");
      }
  }
}

Since it is marked as a product file, it can only be used within defined classes such as Printer within the same file. It cannot be accessed from a different class or a class under a different assembly.

When looking at the access levels of classes and members within a class, first look at the access specifier of the class. In other words, even if we define a member as public, if the class it is connected to is internal, we cannot access the member even if we can access the class from a different assembly.

 

If the access specifier is not specified/defined
If access designators are not specified, specific access designators are assigned by default. For class definitions, the internal access specifier is accepted by default. This means that the class is only accessible within the assembly in which it resides.

For members within the class, the private access specifier is accepted by default. This means that members can only be accessed from within the class in which they are defined.

class Employee
{
  /* ... Codes ... */
}

// The above usage is equivalent to the following by default
// No need to write specially if you are going to use internal anyway
// will get this by default

internal class Employee
{
  /* ... Codes ... */
}
If we examine the examples in the class.

public class Employee
{
  int GetCalculateAge()
  {
    /* ... Codes ... */ 
  }
}

// The above usage is equivalent to the following by default

public class Employee
{
  private int GetCalculateAge()
  {
    /* ... Codes ... */ 
  }
}