Controlling Your Code: An In-Depth Look at C# Modifiers

Welcome to the final lesson in our deep dive into C# Object-Oriented Programming! You've learned about classes, encapsulation, inheritance, polymorphism, and the difference between static and instance members. That's a huge accomplishment!

Throughout this journey, you've seen keywords like public, private, and static. These are called modifiers, and their job is to change the default behavior or accessibility of your classes and their members. Think of them as control switches that give you fine-grained command over your code's architecture.

In this capstone lesson, we'll consolidate everything we know about modifiers and introduce a few more, giving you a complete toolkit for designing professional, robust, and secure C# classes. 🔒

Tommy and Gina are standing on an upper-level balcony inside a research facility, looking at people and robots working in open-area workspaces across three floors of the facility

Access Modifiers – Who Can See Your Code

Access modifiers are keywords that define the visibility of a type (like a class) or a member (like a method or property). They are the gatekeepers of encapsulation, controlling which parts of your code are allowed to interact with other parts.

Public

This is the most permissive access level. A public member can be accessed by any code, anywhere, with no restrictions. It's the "front door" to your class.

Private

This is the most restrictive access level. A private member can only be accessed by code within the same class. This is the default for class members and is perfect for hiding internal data and implementation details.

Protected

This modifier is related to inheritance. A protected member is accessible within its own class and also by any derived (child) classes that inherit from it. Think of it as being for "family members only".

Internal

The internal modifier is very useful for organizing larger projects. An internal member is public within its own project (assembly), but private to any code outside of that assembly. Think of it as an "internal company memo." It's great for creating helper or utility classes for your test framework that you want all your tests within the project to use, but you don't want to expose as part of a public API if you were shipping your framework as a library.

Combined Modifiers: Protected Internal and Private Protected

For even more fine-grained control, you can combine modifiers. These are more advanced, but good to recognize:

  • protected internal: This is a permissive combination. The member is accessible within its own assembly (like internal) OR from a derived class in another assembly (like protected).
  • private protected: This is a restrictive combination. The member is accessible only by derived classes that are located in the same assembly.

You'll primarily use public, private, and protected, but knowing internal exists is very valuable for project organization.

State & Behavior Modifiers

Beyond controlling access, other modifiers change the fundamental behavior and state management of your classes and members.

Static

As we covered in the last lesson, the static modifier makes a member belong to the class itself rather than to any specific instance. There is only one shared copy. We access it via the class name, like Math.PI.

Const

The const keyword declares a constant field whose value must be set at compile time. Once declared, its value can never, ever be changed. A const field is implicitly static.

public class AppSettings
{
    // This value is baked into the compiled code.
    public const int DefaultTimeoutSeconds = 30;
}
 
// Usage:
int timeout = AppSettings.DefaultTimeoutSeconds;    

Use const for true, universal constants whose values are known before the program even runs (like mathematical constants or fixed configuration keys).

Readonly

The readonly keyword declares a field whose value can be set either at the time of declaration or within a constructor of the same class. After the object has been constructed, its value cannot be changed for the lifetime of that object.

public class TestRun
{
    // Each TestRun object will have its own unique, unchangeable ID.
    public readonly Guid RunId;
 
    public TestRun()
    {
        // The value is set at runtime when the object is created.
        RunId = Guid.NewGuid();
    }
}   

This is perfect for instance-specific values that should be immutable after initialization, like a unique ID or a creation timestamp.

Const vs Readonly: Ace the Interview

Distinguishing between const and readonly is a classic C# interview question because it reveals a deep understanding of how C# handles data and object creation. While both create "unchangeable" values, they are fundamentally different.

Here's a concise way to remember the distinction:

Feature const readonly
When is value set? At compile time. Must be a literal value. At runtime (in declaration or constructor).
Scope? Implicitly static (belongs to the class). Instance (belongs to the object).
What can it hold? Only simple built-in types, strings, and null. Any type, including complex objects.

Sealed

The sealed modifier provides a way to "lock down" your class or members.

  • On a class: A sealed class cannot be inherited from. It's the end of the line in an inheritance chain. This is useful when you want to prevent other classes from extending or altering your class's behavior. The famous System.String class in .NET is sealed.
  • On a method or property: When applied to a member that is overriding a base class member (one marked with virtual or abstract), sealed prevents any further derived classes from overriding that member again.
// No other class can inherit from SealedLogger.
public sealed class SealedLogger : SomeBaseLogger
{
    // This method overrides a virtual method from SomeBaseLogger,
    // but because it's sealed, any class that might inherit from
    // SealedLogger (if it weren't sealed) could not override it further.
    public sealed override void Log(string message)
    {
        // ... implementation ...
    }
}   

You use sealed when you want to declare that a class or an implementation is complete and should not be modified through inheritance.

Key Takeaways

  • Access Modifiers (public, private, protected, internal) control the visibility of your classes and members, which is central to encapsulation.
  • The internal modifier makes a type visible within its own project but hidden from external projects, perfect for framework helper classes.
  • const declares a compile-time constant that is implicitly static and cannot be changed after compilation.
  • readonly declares a field whose value is set during object creation (runtime) and cannot be changed afterwards.
  • The sealed modifier prevents a class from being inherited or an overridden member from being further overridden.
  • Understanding these modifiers gives you precise control over the design, security, and behavior of your code.

Mastering Your Modifiers

What's Next?

A huge congratulations on completing the "C# Programming - Object-Oriented Essentials" learning block! This is a major milestone. You now have a powerful understanding of how to design and control professional-grade C# classes using all the core principles of OOP.

With this solid OOP foundation, we're ready to explore more advanced ways to manage groups of objects and query data efficiently. Let's begin the next learning block, Practical Data Structures & LINQ for Testers, starting with a deep dive into two powerful collection types: Dictionaries and HashSets.