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. 🔒
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 (likeinternal) OR from a derived class in another assembly (likeprotected).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
sealedclass 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 famousSystem.Stringclass in .NET is sealed. - On a method or property: When applied to a member that is overriding a base class member (one marked with
virtualorabstract),sealedprevents 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
internalmodifier makes a type visible within its own project but hidden from external projects, perfect for framework helper classes. constdeclares a compile-time constant that is implicitly static and cannot be changed after compilation.readonlydeclares a field whose value is set during object creation (runtime) and cannot be changed afterwards.- The
sealedmodifier 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.