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
227: Debug and Trace Classes
We've all been there. You're staring at a bug that only happens every tenth single time a user hits a specific button, and your first instinct is to pepper your code with Console.WriteLine("I am here 1"); and Console.WriteLine("Now I am here 2");. It's the fastest way to get a pulse on what's happening in your classes, and in a tiny console app, it works fine. But once you start building real-world systems—especially those with multiple classes interacting—that habit becomes a liability.
The "Print-Line" Trap
Imagine you're building a PaymentProcessor class. To track down why some transactions are failing, you add Console.WriteLine calls inside your ProcessPayment method and your ValidateAccount helper. It feels productive until you realize a few things. First, these messages are now hard-coded into your logic. If you ship this to a client, they're going to see your internal debugging chatter in their console or logs. Second, you can't easily turn them off. You have to go back through every class and manually delete or comment out those lines before you commit your code.
// The naive way: Hard-coded, noisy, and permanent public class PaymentProcessor { public void ProcessPayment(PaymentRequest request) { Console.WriteLine($"DEBUG: Processing payment for {request.UserId}"); if (ValidateAccount(request)) { Console.WriteLine("DEBUG: Account validated successfully"); // process payment... } } }This approach mixes your business logic (processing a payment) with your diagnostic logic (reporting that the payment is processing). It's a violation of concerns that makes your code cluttered and harder to maintain.
Separating Intent from Output
The professional way to handle this is by using the
System.Diagnosticsnamespace, specifically theDebugandTraceclasses. To the naked eye,Trace.WriteLine()looks almost exactly likeConsole.WriteLine(), but the plumbing underneath is entirely different. When you useTrace, you aren't telling the program to "print this to the screen right now"; you're telling the program to "emit a diagnostic message."Where that message actually goes is decided by Listeners. By default, it goes to the output window of your IDE, but you can attach a
TextWriterTraceListenerto send those messages to a log file, or a custom listener to send them to a database, all without touching the code inside yourPaymentProcessorclass. I love this because it means I can leave the trace calls in the code permanently, and simply toggle whether they are recorded via a configuration file or a single line of setup inProgram.cs.using System.Diagnostics; public class PaymentProcessor { public void ProcessPayment(PaymentRequest request) { // This intent stays, but the output is configurable Trace.WriteLine($"Processing payment for {request.UserId}"); if (ValidateAccount(request)) { Trace.WriteLine("Account validated successfully"); // process payment... } } }Knowing when to Debug vs. Trace
You'll notice there's both a
Debugclass and aTraceclass. The distinction is subtle but critical for your build pipeline.Debug.WriteLinecalls are only compiled into your assembly when theDEBUGsymbol is defined (typically only in the "Debug" build configuration). When you switch to "Release" mode for production, the compiler literally strips those lines out of the binary. They don't exist in the final product, meaning zero performance hit.
Trace.WriteLine, however, stays in the binary regardless of the build configuration. I useDebugfor "developer-only" noise—things like verifying a loop ran the correct number of times. I useTracefor high-level operational milestones—like "Payment Gateway Connection Established"—that I might actually need to see in a production log file when a customer reports an error. If you useDebugfor everything, you'll find yourself blind the moment you deploy to a production server.
📋 Practical Task
Refactoring the OrderManager Audit Log
You have been handed a OrderManager class that is currently using Console.WriteLine to track order status changes. This is polluting the production console and cannot be turned off. Your task is to refactor this into a professional tracing system.
Requirements:
- Replace all
Console.WriteLinecalls in theOrderManagerclass withTrace.WriteLine. - In the
Mainmethod of the program, add aTextWriterTraceListenerthat redirects all trace output to a file namedorder_log.txt. - Add one
Debug.WriteLinecall that only triggers when an order is "Internal" (a developer-only check) to ensure it doesn't appear in the final release build. - Call
Trace.Flush()at the end of the program to ensure the file is written correctly.
using System;
using System.Diagnostics;
using System.IO;
public class OrderManager
{
public void UpdateOrderStatus(int orderId, string status, bool isInternal)
{
// TODO: Refactor this line
Console.WriteLine($"Order {orderId} changed status to {status}");
if (isInternal)
{
// TODO: Add a Debug trace here for internal orders
}
}
}
public class Program
{
public static void Main()
{
// TODO: Configure the TraceListener to write to "order_log.txt"
OrderManager manager = new OrderManager();
manager.UpdateOrderStatus(101, "Shipped", false);
manager.UpdateOrderStatus(102, "Pending", true);
// TODO: Flush the trace
}
}
There are no comments for now.