C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
162: Access Modifiers: public, private, protected, internal
In a real-world project, you'll quickly realize that making everything public is a recipe for disaster. When you're working on a large codebase with other developers, you want to control exactly who can touch which part of your logic. This is where access modifiers come in—they're essentially the "keep out" signs of your code.
Keeping the internal wiring hidden
Let's imagine we're building a system for a smart home. We'll start with a base SmartDevice class. I want the outside world to be able to turn the device on and off, but I absolutely do not want them messing with the internal voltage or the hardware status codes.
public class SmartDevice
{
public string DeviceName { get; set; }
private double _currentVoltage = 110.0;
public void PowerOn()
{
Console.WriteLine($"{DeviceName} is now powered on.");
// I can access _currentVoltage here because we're inside the class
CheckVoltage();
}
private void CheckVoltage()
{
Console.WriteLine($"Voltage is stable at {_currentVoltage}V.");
}
}
By marking _currentVoltage and CheckVoltage() as private, I've ensured that no other class can accidentally change the voltage or trigger a hardware check. It's encapsulated. If I tried to call device.CheckVoltage() from my main program, the compiler would slap my wrist immediately.
Sharing secrets with child classes
Now, here's where I usually make a mistake when I'm rushing. I'll create a SmartLight class that inherits from SmartDevice. I want the light to be able to access a unique DeviceId for registration purposes.
public class SmartDevice
{
public string DeviceName { get; set; }
public string DeviceId = "DEV-12345"; // Wait, I made this public...
// ... other code
}
public class SmartLight : SmartDevice
{
public void ConnectToWifi()
{
Console.WriteLine($"Connecting device {DeviceId} to network...");
}
}
I just realized I made DeviceId public. That's a problem. While it allows SmartLight to use the ID, it also allows any random part of the application to overwrite the DeviceId while the program is running. That's a huge bug waiting to happen.
The fix is to use protected. This modifier says: "The outside world can't see this, but any class that inherits from me can."
public class SmartDevice
{
public string DeviceName { get; set; }
protected string DeviceId = "DEV-12345"; // Fixed!
// ... other code
}
Now, SmartLight can still use DeviceId in its ConnectToWifi method, but if you try to access myLight.DeviceId from your Main method, it will fail. It's a much safer way to handle inheritance.
Handling assembly-wide access
Finally, there's internal. This one is a bit different because it's not about the class hierarchy, but about the project (the assembly) itself. Imagine we have a DeviceRegistry class that handles the database of all devices in the house.
public class DeviceRegistry
{
internal void RegisterDeviceInternal(SmartDevice device)
{
Console.WriteLine($"Logging {device.DeviceName} to the system database.");
}
}
By marking RegisterDeviceInternal as internal, I'm saying that any class within this same project/DLL can call this method, but if someone else references my compiled library in a separate project, they won't even see this method exists. It's perfect for "helper" logic that is critical for the library to function but shouldn't be exposed as part of the public API.
- public: Open to everyone.
- private: Only accessible inside the current class.
- protected: Accessible inside the class and its children.
- internal: Accessible anywhere within the same assembly (project).
📋 Practical Task
Exercise: Implementing a SecureDigitalWallet with Tiered Access
Create a system that simulates a digital wallet. You need to implement the following requirements using the correct access modifiers:
- Create a base class called
Wallet. - The
OwnerNameshould bepublic. - The
_balanceshould beprivateso it cannot be modified directly from outside the class. - Create a
protectedmethod calledApplyTransactionFee()that deducts a small amount from the balance. - Create a
publicmethod calledDeposit()that increases the balance. - Create a derived class called
CryptoWalletthat inherits fromWallet. - In
CryptoWallet, create apublicmethod calledSendCrypto()that calls theprotectedApplyTransactionFee()method before deducting the amount from the balance. - Create an
internalclass calledWalletAuditLogwith a method that logs the wallet's activity (this represents a system-level tool).
Verify that you cannot access the _balance or ApplyTransactionFee() directly from your Main method.
There are no comments for now.