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
215: Preprocessor Directives: #if, #region, #pragma
How do I run different code during debugging versus production?
You've probably noticed that during development, you want tons of detailed logs, but in a production environment, those same logs would kill your performance or leak sensitive data. This is where #if comes in. Unlike a standard if statement, which is evaluated while the program is running, a preprocessor directive is evaluated by the compiler before the code is even turned into IL.
If the condition isn't met, the compiler literally acts as if that code doesn't exist. It won't even be in the final DLL.
public void ProcessPayment(Order order)
{
#if DEBUG
Console.WriteLine($"[DEBUG] Processing payment for Order {order.Id}. Internal state: {order.InternalDebugInfo}");
#endif
PaymentGateway.Charge(order.Amount);
}
In the example above, the Console.WriteLine only exists in your Debug build. When you switch to Release mode, that line is stripped out entirely. I usually use this for heavy telemetry or "sanity check" assertions that would be too slow for a real user to encounter.
Is #region actually useful or just a way to hide messy code?
To be honest, this is a debated topic among engineers. Some people think #region is a "code smell" because if you need to collapse your code to make it readable, your class is probably too long. But in the real world, we often deal with massive legacy files or classes that have a lot of boilerplate properties.
I use regions to group related private helper methods or to separate the "public API" of a class from its internal implementation details. It's purely for the human reading the code; the compiler ignores it completely.
public class DatabaseManager
{
#region Connection Logic
private void OpenConnection() { /* ... */ }
private void CloseConnection() { /* ... */ }
private void ValidateCredentials() { /* ... */ }
#endregion
#region Data Queries
public User GetUser(int id) { /* ... */ }
public List<Order> GetOrders() { /* ... */ }
#endregion
}
Just a word of advice: don't overdo it. If you have ten nested regions, you're not organizing your code—you're just hiding the chaos.
How do I stop the compiler from nagging me about a specific warning?
Sometimes you know exactly why you're doing something, but the compiler still throws a warning. Maybe you're implementing an interface where one method is intentionally left blank, or you're using a deprecated API because the new one isn't supported on an older OS you still have to target.
You can use #pragma warning to tell the compiler to shut up for a moment. The key is to disable the warning, do the "risky" thing, and then immediately re-enable it so you don't accidentally ignore that warning everywhere else in your project.
public void LegacyCleanup()
{
#pragma warning disable CS0168 // Variable declared but never used
string oldSystemToken = "ABC-123";
// I need this here for a specific reflection-based tool,
// even though I don't use it in the code logic.
#pragma warning restore CS0168
}
I really recommend adding a comment (like I did above) explaining why you're disabling the warning. Six months from now, you won't remember why you silenced it, and your future self will thank you for the note.
📋 Practical Task
Exercise: Building a Platform-Aware Logger
You are building a logging utility that needs to behave differently based on the build configuration and a custom symbol. Complete the following requirements:
- Create a class named
DiagnosticLogger. - Add a method
LogMessage(string message). - Inside
LogMessage, use#if DEBUGto print the message to the console with a "DEBUG: " prefix. - Use
#elseto print the message with an "INFO: " prefix for non-debug builds. - Create a private method called
InternalTrace()that is intentionally empty. Use#pragma warning disable CS0168and#pragma warning restore CS0168inside this method to silence a "variable declared but never used" warning for a local variable you create inside it. - Wrap all your private helper methods (including
InternalTrace) inside a#regiontitled "Internal Helpers".
There are no comments for now.