Debugging .NET Applications

#dotnet #debugging #troubleshooting #performance #logging #profiling

Last Updated: May 18, 2025 Related: .NET vs Laravel Complete Developer Guide


Quick Navigation


Development Debugging

๐Ÿ” Visual Studio Debugging Features

Essential Debugging Windows

Debug โ†’ Windows โ†’ 
โ”œโ”€โ”€ Locals           # Current scope variables
โ”œโ”€โ”€ Watch            # Custom expressions
โ”œโ”€โ”€ Call Stack       # Execution path
โ”œโ”€โ”€ Immediate        # Execute code during debugging
โ”œโ”€โ”€ Autos            # Automatic variable tracking
โ”œโ”€โ”€ Threads          # Thread information
โ”œโ”€โ”€ Modules          # Loaded assemblies
โ””โ”€โ”€ Exception Settings # Exception configuration

Advanced Breakpoints

// Conditional breakpoints
// Right-click breakpoint โ†’ Conditions
// Condition: user.Id == 123
// Hit count: Break when hit count is multiple of 10

// Tracepoints (logging without stopping)
// Right-click breakpoint โ†’ Actions
// Log message: "User {user.Name} processed at {DateTime.Now}"

// Data breakpoints (break when value changes)
// Debug โ†’ New Breakpoint โ†’ Data Breakpoint
// Address: &variableName

Debugging Attributes

// Control stepping behavior
[DebuggerStepThrough]  // Skip this method when stepping
public static void UtilityMethod()
{
    // Implementation details debugger will skip
}

// Custom debugger display
[DebuggerDisplay("User: {Name} ({Id})")]
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// Hide from debugger
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string _internalState;

// Custom type proxy for complex objects
[DebuggerTypeProxy(typeof(DebugView))]
public class ComplexObject
{
    internal class DebugView
    {
        private readonly ComplexObject _obj;
        public DebugView(ComplexObject obj) => _obj = obj;
        
        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        public string Summary => $"Count: {_obj.Items.Count}";
    }
}

๐Ÿ› ๏ธ VS Code Debugging

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": ".NET Core Launch (web)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/bin/Debug/net8.0/MyApp.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            "serverReadyAction": {
                "action": "openExternally",
                "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
            },
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Development",
                "ASPNETCORE_URLS": "https://localhost:5001"
            },
            "sourceFileMap": {
                "/Views": "${workspaceFolder}/Views"
            }
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }
    ]
}

// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "process",
            "args": [
                "build",
                "${workspaceFolder}/MyApp.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "group": "build",
            "presentation": {
                "reveal": "silent"
            },
            "problemMatcher": "$msCompile"
        }
    ]
}

๐Ÿ“ Structured Logging for Debugging

// Serilog configuration for development
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .Enrich.FromLogContext()
    .Enrich.WithProperty("Application", "MyApp")
    .WriteTo.Console(
        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
    .WriteTo.File("logs/debug-.log", 
        rollingInterval: RollingInterval.Day,
        retainedFileCountLimit: 7)
    .WriteTo.Seq("http://localhost:5341")  // Optional: Seq for structured logs
    .CreateLogger();

// Usage in code
public class UserService
{
    private readonly ILogger<UserService> _logger;
    
    public async Task<User> CreateUserAsync(CreateUserRequest request)
    {
        using var activity = _logger.BeginScope("Creating user {Email}", request.Email);
        
        _logger.LogDebug("Validating user data");
        
        try
        {
            var user = new User { Email = request.Email };
            await _repository.SaveAsync(user);
            
            _logger.LogInformation("User created successfully with ID {UserId}", user.Id);
            return user;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to create user {Email}", request.Email);
            throw;
        }
    }
}

// Custom log enrichers
public class RequestEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
    {
        var httpContext = // Get from current context
        if (httpContext != null)
        {
            logEvent.AddPropertyIfAbsent(factory.CreateProperty(
                "RequestId", httpContext.TraceIdentifier));
            logEvent.AddPropertyIfAbsent(factory.CreateProperty(
                "UserAgent", httpContext.Request.Headers["User-Agent"].ToString()));
        }
    }
}

Production Diagnostics

๐Ÿ“Š Application Insights Integration

// Startup configuration
builder.Services.AddApplicationInsightsTelemetry(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("ApplicationInsights");
    options.DeveloperMode = builder.Environment.IsDevelopment();
});

// Custom telemetry
public class OrderService
{
    private readonly TelemetryClient _telemetryClient;
    
    public async Task ProcessOrderAsync(Order order)
    {
        using var operation = _telemetryClient.StartOperation<RequestTelemetry>("ProcessOrder");
        operation.Telemetry.Properties["OrderId"] = order.Id.ToString();
        operation.Telemetry.Properties["CustomerId"] = order.CustomerId.ToString();
        
        try
        {
            // Process order
            await _paymentService.ChargeAsync(order);
            
            _telemetryClient.TrackEvent("OrderProcessed", new Dictionary<string, string>
            {
                ["OrderId"] = order.Id.ToString(),
                ["Amount"] = order.Total.ToString("C")
            });
        }
        catch (Exception ex)
        {
            _telemetryClient.TrackException(ex, new Dictionary<string, string>
            {
                ["OrderId"] = order.Id.ToString(),
                ["Stage"] = "Payment"
            });
            throw;
        }
    }
}

// Custom metrics
_telemetryClient.TrackMetric("OrderProcessingTime", stopwatch.ElapsedMilliseconds);
_telemetryClient.TrackDependency("SQL", "GetUser", "SELECT * FROM Users", startTime, stopwatch.Elapsed, success);

๐Ÿฅ Health Checks for Diagnostics

// Comprehensive health check setup
builder.Services.AddHealthChecks()
    .AddDbContext<ApplicationDbContext>(name: "database")
    .AddRedis(builder.Configuration.GetConnectionString("Redis"), name: "redis")
    .AddCheck<ApiHealthCheck>("external-api")
    .AddCheck<DiskSpaceHealthCheck>("disk-space")
    .AddCheck<MemoryHealthCheck>("memory");

// Custom health checks
public class ApiHealthCheck : IHealthCheck
{
    private readonly HttpClient _httpClient;
    private readonly IConfiguration _config;
    
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _httpClient.GetAsync(
                _config["ExternalApi:HealthEndpoint"], 
                cancellationToken);
            
            return response.IsSuccessStatusCode
                ? HealthCheckResult.Healthy("External API is responsive")
                : HealthCheckResult.Unhealthy($"External API returned {response.StatusCode}");
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("External API is unreachable", ex);
        }
    }
}

public class MemoryHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        var process = Process.GetCurrentProcess();
        var memoryUsedMB = process.WorkingSet64 / 1024 / 1024;
        
        var data = new Dictionary<string, object>
        {
            { "MemoryUsedMB", memoryUsedMB },
            { "Gen0Collections", GC.CollectionCount(0) },
            { "Gen1Collections", GC.CollectionCount(1) },
            { "Gen2Collections", GC.CollectionCount(2) }
        };
        
        return memoryUsedMB > 500
            ? Task.FromResult(HealthCheckResult.Degraded("High memory usage", null, data))
            : Task.FromResult(HealthCheckResult.Healthy("Memory usage normal", data));
    }
}

// Health check endpoints
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false
});

๐Ÿ” dotnet-dump and dotnet-trace

# Install diagnostic tools
dotnet tool install --global dotnet-dump
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-gcdump

# List running .NET processes
dotnet-trace ps

# Capture memory dump
dotnet-dump collect -p <process-id>
dotnet-dump collect -n <process-name>

# Analyze dump file
dotnet-dump analyze <dump-file>
> dumpheap -stat                    # Show object statistics
> dumpheap -type System.String      # Show all strings
> gcroot <object-address>           # Find GC roots
> clrstack                          # Show managed call stack

# Performance tracing
dotnet-trace collect -p <process-id> --duration 00:00:30
dotnet-trace convert trace.nettrace # Convert to various formats

# Real-time performance counters
dotnet-counters monitor -p <process-id>
dotnet-counters monitor --counters Microsoft.AspNetCore.Hosting -p <process-id>

# GC dump analysis
dotnet-gcdump collect -p <process-id>

Performance Debugging

โšก Profiling with BenchmarkDotNet

[MemoryDiagnoser]
[ThreadingDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class StringComparisonBenchmark
{
    private readonly string[] _strings = Enumerable.Range(0, 1000)
        .Select(i => $"TestString{i}")
        .ToArray();
    
    [Benchmark(Baseline = true)]
    public int StringEquals()
    {
        int count = 0;
        foreach (var str in _strings)
        {
            if (str.Equals("TestString500"))
                count++;
        }
        return count;
    }
    
    [Benchmark]
    public int StringComparison()
    {
        int count = 0;
        foreach (var str in _strings)
        {
            if (string.Equals(str, "TestString500", StringComparison.Ordinal))
                count++;
        }
        return count;
    }
    
    [Benchmark]
    public int StringContains()
    {
        return _strings.Count(s => s.Contains("TestString500"));
    }
}

// Run benchmarks
class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<StringComparisonBenchmark>();
        
        // Results show method performance, memory allocation, etc.
    }
}

๐Ÿ“ˆ Custom Performance Counters

// Performance counter creation
public class PerformanceTracker
{
    private readonly Counter<int> _requestCounter;
    private readonly Histogram<double> _requestDuration;
    private readonly UpDownCounter<int> _activeUsers;
    
    public PerformanceTracker(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("MyApp.Performance");
        
        _requestCounter = meter.CreateCounter<int>(
            "requests_total",
            "count",
            "Total number of requests");
            
        _requestDuration = meter.CreateHistogram<double>(
            "request_duration_seconds",
            "seconds",
            "Request duration in seconds");
            
        _activeUsers = meter.CreateUpDownCounter<int>(
            "active_users",
            "count",
            "Number of active users");
    }
    
    public void RecordRequest(string endpoint, double durationSeconds)
    {
        _requestCounter.Add(1, new TagList { { "endpoint", endpoint } });
        _requestDuration.Record(durationSeconds, new TagList { { "endpoint", endpoint } });
    }
    
    public void UserConnected() => _activeUsers.Add(1);
    public void UserDisconnected() => _activeUsers.Add(-1);
}

// Middleware for automatic tracking
public class PerformanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly PerformanceTracker _tracker;
    
    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            await _next(context);
        }
        finally
        {
            stopwatch.Stop();
            _tracker.RecordRequest(
                context.Request.Path, 
                stopwatch.Elapsed.TotalSeconds);
        }
    }
}

๐Ÿ” Entity Framework Performance Debugging

// Enable sensitive data logging for development
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (_environment.IsDevelopment())
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors()
            .LogTo(Console.WriteLine, LogLevel.Information);
    }
}

// Query analysis
public class UserRepository
{
    public async Task<List<User>> GetActiveUsersWithPostsAsync()
    {
        // โŒ Bad: N+1 query problem
        var users = await _context.Users.Where(u => u.IsActive).ToListAsync();
        foreach (var user in users)
        {
            user.Posts = await _context.Posts.Where(p => p.UserId == user.Id).ToListAsync();
        }
        
        // โœ… Good: Single query with Include
        return await _context.Users
            .Where(u => u.IsActive)
            .Include(u => u.Posts)
            .ToListAsync();
            
        // โœ… Better: Projection for better performance
        return await _context.Users
            .Where(u => u.IsActive)
            .Select(u => new User
            {
                Id = u.Id,
                Name = u.Name,
                Posts = u.Posts.Where(p => p.IsPublished).ToList()
            })
            .ToListAsync();
    }
}

// Query splitting for large includes
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(b => b.Posts)
        .WithOne(p => p.Blog)
        .HasForeignKey(p => p.BlogId);
        
    // Enable split queries globally
    modelBuilder.SetDefaultSplitQueries(true);
}

// Or per query
var blogs = await context.Blogs
    .AsSplitQuery()
    .Include(blog => blog.Posts)
    .ToListAsync();

Memory Issues

๐Ÿง  Memory Leak Detection

// Event handler memory leaks
public class BadEventSubscriber
{
    public BadEventSubscriber(EventPublisher publisher)
    {
        // โŒ Bad: Creates strong reference, prevents GC
        publisher.SomethingHappened += HandleEvent;
    }
    
    private void HandleEvent(object sender, EventArgs e)
    {
        // Handle event
    }
}

public class GoodEventSubscriber : IDisposable
{
    private readonly EventPublisher _publisher;
    
    public GoodEventSubscriber(EventPublisher publisher)
    {
        _publisher = publisher;
        // โœ… Good: Remove subscription in Dispose
        _publisher.SomethingHappened += HandleEvent;
    }
    
    private void HandleEvent(object sender, EventArgs e)
    {
        // Handle event
    }
    
    public void Dispose()
    {
        _publisher.SomethingHappened -= HandleEvent;
    }
}

// WeakReference for caching
public class CacheManager
{
    private readonly ConcurrentDictionary<string, WeakReference<object>> _cache = new();
    
    public T Get<T>(string key) where T : class
    {
        if (_cache.TryGetValue(key, out var weakRef) && 
            weakRef.TryGetTarget(out var target))
        {
            return (T)target;
        }
        return null;
    }
    
    public void Set<T>(string key, T value) where T : class
    {
        _cache[key] = new WeakReference<object>(value);
    }
}

// Memory diagnostic helper
public static class MemoryDiagnostics
{
    public static void LogMemoryUsage(ILogger logger)
    {
        var process = Process.GetCurrentProcess();
        var memInfo = new
        {
            WorkingSetMB = process.WorkingSet64 / 1024 / 1024,
            PrivateMemoryMB = process.PrivateMemorySize64 / 1024 / 1024,
            VirtualMemoryMB = process.VirtualMemorySize64 / 1024 / 1024,
            ManagedMemoryMB = GC.GetTotalMemory(false) / 1024 / 1024,
            Gen0Collections = GC.CollectionCount(0),
            Gen1Collections = GC.CollectionCount(1),
            Gen2Collections = GC.CollectionCount(2)
        };
        
        logger.LogInformation("Memory usage: {@MemoryInfo}", memInfo);
    }
    
    public static void ForceGarbageCollection()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
}

๐Ÿ” Memory Dump Analysis

# Analyze memory dump with dotnet-dump
dotnet-dump analyze memory.dmp

# Common commands in dump analysis
> help                              # Show all commands
> dumpheap -stat                   # Object statistics
> dumpheap -type System.String     # All string objects
> dumpheap -mt <method-table>      # Objects of specific type
> gcroot <address>                 # Find what's keeping object alive
> finalizequeue                    # Objects waiting for finalization
> syncblk                          # Synchronization blocks
> threadpool                       # Thread pool information
> clrthreads                       # Managed threads
> dumpmodule -mt <module>          # Method tables in module

# Example investigation
> dumpheap -stat
# Look for types with high count/size
> dumpheap -type MyApp.Models.User
# Get addresses of User objects
> gcroot 0x000001a2b3c4d5e6
# See what's holding references to prevent GC

Async & Threading Issues

๐Ÿ”€ Deadlock Detection and Prevention

// โŒ Bad: Deadlock prone code
public class DeadlockService
{
    public string GetDataSync()
    {
        // This can deadlock in ASP.NET
        return GetDataAsync().Result;
    }
    
    public async Task<string> GetDataAsync()
    {
        await Task.Delay(100);
        return "data";
    }
}

// โœ… Good: Async all the way
public class AsyncService
{
    public async Task<string> GetDataAsync()
    {
        // Use ConfigureAwait(false) in libraries
        await Task.Delay(100).ConfigureAwait(false);
        return "data";
    }
}

// Deadlock detection utility
public static class DeadlockDetector
{
    private static readonly ConcurrentDictionary<int, DateTime> _operations = new();
    
    public static async Task<T> WithDeadlockDetection<T>(
        Func<Task<T>> operation,
        int timeoutMs = 30000,
        [CallerMemberName] string operationName = "")
    {
        var threadId = Thread.CurrentThread.ManagedThreadId;
        var startTime = DateTime.UtcNow;
        _operations[threadId] = startTime;
        
        try
        {
            using var cts = new CancellationTokenSource(timeoutMs);
            var task = operation();
            var completedTask = await Task.WhenAny(task, Task.Delay(timeoutMs, cts.Token));
            
            if (completedTask == task)
            {
                return await task;
            }
            else
            {
                throw new TimeoutException(
                    $"Operation '{operationName}' timed out after {timeoutMs}ms. " +
                    $"Possible deadlock on thread {threadId}");
            }
        }
        finally
        {
            _operations.TryRemove(threadId, out _);
        }
    }
    
    public static void LogLongRunningOperations(ILogger logger)
    {
        var threshold = TimeSpan.FromSeconds(10);
        var now = DateTime.UtcNow;
        
        foreach (var kvp in _operations)
        {
            if (now - kvp.Value > threshold)
            {
                logger.LogWarning(
                    "Long-running operation detected on thread {ThreadId}, " +
                    "running for {Duration}ms",
                    kvp.Key, (now - kvp.Value).TotalMilliseconds);
            }
        }
    }
}

๐Ÿงต Thread Pool Analysis

// Thread pool monitoring
public class ThreadPoolMonitor
{
    private readonly ILogger<ThreadPoolMonitor> _logger;
    private readonly Timer _timer;
    
    public ThreadPoolMonitor(ILogger<ThreadPoolMonitor> logger)
    {
        _logger = logger;
        _timer = new Timer(LogThreadPoolStats, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
    }
    
    private void LogThreadPoolStats(object state)
    {
        ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableCompletionPortThreads);
        ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads);
        ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads);
        
        var busyWorkerThreads = maxWorkerThreads - availableWorkerThreads;
        var busyCompletionPortThreads = maxCompletionPortThreads - availableCompletionPortThreads;
        
        _logger.LogInformation(
            "ThreadPool Stats - Worker: {BusyWorkerThreads}/{MaxWorkerThreads}, " +
            "IO: {BusyCompletionPortThreads}/{MaxCompletionPortThreads}",
            busyWorkerThreads, maxWorkerThreads,
            busyCompletionPortThreads, maxCompletionPortThreads);
            
        // Alert if thread pool is under pressure
        if (availableWorkerThreads < maxWorkerThreads * 0.1)
        {
            _logger.LogWarning("Thread pool pressure detected: Only {AvailableThreads} worker threads available",
                availableWorkerThreads);
        }
    }
    
    public void Dispose()
    {
        _timer?.Dispose();
    }
}

// Custom SynchronizationContext for testing
public class TestSynchronizationContext : SynchronizationContext
{
    private readonly Queue<(SendOrPostCallback callback, object state)> _queue = new();
    private readonly object _lock = new();
    
    public override void Post(SendOrPostCallback d, object state)
    {
        lock (_lock)
        {
            _queue.Enqueue((d, state));
        }
    }
    
    public void ExecuteAll()
    {
        while (true)
        {
            (SendOrPostCallback callback, object state) item;
            
            lock (_lock)
            {
                if (_queue.Count == 0) break;
                item = _queue.Dequeue();
            }
            
            item.callback(item.state);
        }
    }
}

Network & HTTP Debugging

๐ŸŒ HTTP Client Debugging

// HTTP request/response logging
public class LoggingHandler : DelegatingHandler
{
    private readonly ILogger<LoggingHandler> _logger;
    
    public LoggingHandler(ILogger<LoggingHandler> logger) : base()
    {
        _logger = logger;
    }
    
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        var requestId = Guid.NewGuid().ToString();
        
        // Log request
        _logger.LogInformation(
            "Sending HTTP request {RequestId}: {Method} {Uri}",
            requestId, request.Method, request.RequestUri);
            
        if (request.Content != null)
        {
            var content = await request.Content.ReadAsStringAsync();
            _logger.LogDebug("Request {RequestId} body: {Content}", requestId, content);
        }
        
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            var response = await base.SendAsync(request, cancellationToken);
            stopwatch.Stop();
            
            _logger.LogInformation(
                "Received HTTP response {RequestId}: {StatusCode} in {ElapsedMs}ms",
                requestId, response.StatusCode, stopwatch.ElapsedMilliseconds);
                
            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await response.Content.ReadAsStringAsync();
                _logger.LogWarning(
                    "HTTP request {RequestId} failed with {StatusCode}: {Content}",
                    requestId, response.StatusCode, errorContent);
            }
            
            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex,
                "HTTP request {RequestId} failed after {ElapsedMs}ms",
                requestId, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

// Register the handler
builder.Services.AddHttpClient<ApiService>(client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.Timeout = TimeSpan.FromSeconds(30);
})
.AddHttpMessageHandler<LoggingHandler>();

// Circuit breaker pattern for resilience
public class CircuitBreakerHandler : DelegatingHandler
{
    private readonly int _failureThreshold;
    private readonly TimeSpan _timeout;
    private int _failureCount;
    private DateTime _lastFailureTime;
    private CircuitState _state = CircuitState.Closed;
    
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        if (_state == CircuitState.Open)
        {
            if (DateTime.UtcNow - _lastFailureTime > _timeout)
            {
                _state = CircuitState.HalfOpen;
            }
            else
            {
                throw new HttpRequestException("Circuit breaker is open");
            }
        }
        
        try
        {
            var response = await base.SendAsync(request, cancellationToken);
            
            if (response.IsSuccessStatusCode)
            {
                _failureCount = 0;
                _state = CircuitState.Closed;
            }
            
            return response;
        }
        catch
        {
            _failureCount++;
            _lastFailureTime = DateTime.UtcNow;
            
            if (_failureCount >= _failureThreshold)
            {
                _state = CircuitState.Open;
            }
            
            throw;
        }
    }
    
    private enum CircuitState { Closed, Open, HalfOpen }
}

๐Ÿ” Network Tracing

// Enable .NET network tracing
// App.config or through environment variables
<system.diagnostics>
  <trace autoflush="true" />
  <sources>
    <source name="System.Net" switchValue="Information">
      <listeners>
        <add name="System.Net" type="System.Diagnostics.TextWriterTraceListener" 
             initializeData="network.log" />
      </listeners>
    </source>
    <source name="System.Net.Sockets" switchValue="Information">
      <listeners>
        <add name="System.Net.Sockets" type="System.Diagnostics.TextWriterTraceListener" 
             initializeData="sockets.log" />
      </listeners>
    </source>
  </sources>
</system.diagnostics>

// Environment variables for .NET Core
// DOTNET_System_Net_SocketsTrace=true
// DOTNET_System_Net_Http_SocketsHttpHandler_Debug=true

// Custom network diagnostics
public class NetworkDiagnostics
{
    public static async Task<NetworkLatency> MeasureLatencyAsync(string host, int port = 80)
    {
        var ping = new Ping();
        var pingReply = await ping.SendPingAsync(host);
        
        var tcpStopwatch = Stopwatch.StartNew();
        using var tcpClient = new TcpClient();
        
        try
        {
            await tcpClient.ConnectAsync(host, port);
            tcpStopwatch.Stop();
            
            return new NetworkLatency
            {
                Host = host,
                PingLatency = pingReply.RoundtripTime,
                TcpConnectLatency = tcpStopwatch.ElapsedMilliseconds,
                Status = pingReply.Status
            };
        }
        catch (Exception ex)
        {
            tcpStopwatch.Stop();
            return new NetworkLatency
            {
                Host = host,
                PingLatency = pingReply.RoundtripTime,
                TcpConnectLatency = -1,
                Status = pingReply.Status,
                Error = ex.Message
            };
        }
    }
}

public class NetworkLatency
{
    public string Host { get; set; }
    public long PingLatency { get; set; }
    public long TcpConnectLatency { get; set; }
    public IPStatus Status { get; set; }
    public string Error { get; set; }
}

Advanced Debugging Techniques

๐Ÿ”ฌ Source Generators Debug Support

// Enable source generator debugging
// .csproj configuration
<PropertyGroup>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

// Debug source generator
[Generator]
public class ApiControllerGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // Add debugger launch for development
        #if DEBUG
        if (!System.Diagnostics.Debugger.IsAttached)
        {
            System.Diagnostics.Debugger.Launch();
        }
        #endif
        
        // Generator implementation
        var source = GenerateControllerCode();
        context.AddSource("GeneratedController.g.cs", source);
    }
    
    public void Initialize(GeneratorInitializationContext context)
    {
        // Initialize
    }
}

// Debugging generated code
public partial class ApiController
{
    // This will be generated by source generator
    // Use "Go to Definition" to see generated code
}

๐ŸŽญ Aspect-Oriented Debugging

// Using Castle DynamicProxy for debugging
public class DebugInterceptor : IInterceptor
{
    private readonly ILogger<DebugInterceptor> _logger;
    
    public void Intercept(IInvocation invocation)
    {
        var methodName = $"{invocation.TargetType.Name}.{invocation.Method.Name}";
        var parameters = string.Join(", ", 
            invocation.Arguments.Select(a => a?.ToString() ?? "null"));
        
        _logger.LogDebug("Entering {MethodName}({Parameters})", methodName, parameters);
        
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            invocation.Proceed();
            stopwatch.Stop();
            
            _logger.LogDebug("Exiting {MethodName} after {ElapsedMs}ms", 
                methodName, stopwatch.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, "Exception in {MethodName} after {ElapsedMs}ms", 
                methodName, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

// Registration with DI container
builder.Services.AddScoped<DebugInterceptor>();
builder.Services.AddScoped<IUserService>(provider =>
{
    var proxyGenerator = new ProxyGenerator();
    var target = new UserService(/* dependencies */);
    var interceptor = provider.GetService<DebugInterceptor>();
    
    return proxyGenerator.CreateInterfaceProxyWithTarget<IUserService>(target, interceptor);
});

๐Ÿ”ง Custom Diagnostics Tools

// Custom diagnostic command for dotnet-dump
public static class CustomDiagnostics
{
    [Conditional("DEBUG")]
    public static void DumpObjectGraph(object obj, string fileName = null)
    {
        fileName ??= $"object-graph-{DateTime.Now:yyyyMMdd-HHmmss}.json";
        
        var graph = BuildObjectGraph(obj, new HashSet<object>());
        var json = JsonSerializer.Serialize(graph, new JsonSerializerOptions
        {
            WriteIndented = true,
            ReferenceHandler = ReferenceHandler.IgnoreCycles
        });
        
        File.WriteAllText(fileName, json);
    }
    
    private static object BuildObjectGraph(object obj, HashSet<object> visited)
    {
        if (obj == null || visited.Contains(obj))
            return null;
            
        visited.Add(obj);
        
        var type = obj.GetType();
        if (type.IsPrimitive || type == typeof(string))
            return obj;
            
        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanRead)
            .ToDictionary(
                p => p.Name,
                p => {
                    try
                    {
                        var value = p.GetValue(obj);
                        return BuildObjectGraph(value, visited);
                    }
                    catch
                    {
                        return "[Error reading property]";
                    }
                });
                
        return new { Type = type.Name, Properties = properties };
    }
}

// Performance boundary tracking
public class PerformanceBoundary : IDisposable
{
    private readonly string _operationName;
    private readonly ILogger _logger;
    private readonly Stopwatch _stopwatch;
    private readonly IDisposable _scope;
    
    public PerformanceBoundary(string operationName, ILogger logger)
    {
        _operationName = operationName;
        _logger = logger;
        _stopwatch = Stopwatch.StartNew();
        _scope = _logger.BeginScope("Operation: {OperationName}", operationName);
        
        _logger.LogDebug("Started {OperationName}", operationName);
    }
    
    public void Dispose()
    {
        _stopwatch.Stop();
        _logger.LogDebug("Completed {OperationName} in {ElapsedMs}ms", 
            _operationName, _stopwatch.ElapsedMilliseconds);
        _scope?.Dispose();
    }
}

// Usage
public async Task<User> GetUserAsync(int id)
{
    using var perf = new PerformanceBoundary(nameof(GetUserAsync), _logger);
    
    // Implementation
    return await _repository.GetByIdAsync(id);
}

Debugging Checklists

โœ… Production Issue Investigation

## Production Issue Investigation Checklist

### Initial Assessment
- [ ] Gather error messages and stack traces
- [ ] Check application logs for patterns
- [ ] Review monitoring dashboards (CPU, memory, requests)
- [ ] Identify affected users/features
- [ ] Determine timeline of issue onset

### Data Collection
- [ ] Capture memory dump if memory-related
- [ ] Collect performance traces
- [ ] Export relevant log segments
- [ ] Screenshot error pages/UI issues
- [ ] Gather user reproduction steps

### Analysis
- [ ] Analyze memory dumps for leaks/exceptions
- [ ] Review performance traces for bottlenecks
- [ ] Search logs for error patterns
- [ ] Compare with baseline performance metrics
- [ ] Check recent deployments/changes

### Resolution
- [ ] Develop hypothesis based on findings
- [ ] Create minimal reproduction in dev environment
- [ ] Implement fix with tests
- [ ] Verify fix in staging environment
- [ ] Deploy with monitoring plan

### Follow-up
- [ ] Monitor metrics post-deployment
- [ ] Update alerts/monitoring if needed
- [ ] Document root cause and solution
- [ ] Consider preventive measures
- [ ] Share learnings with team

๐Ÿ” Performance Investigation Checklist

## Performance Investigation Checklist

### Baseline Establishment
- [ ] Collect current performance metrics
- [ ] Identify performance SLAs/targets
- [ ] Establish test scenarios
- [ ] Document current user experience

### Profiling
- [ ] CPU profiling for hot paths
- [ ] Memory profiling for allocations
- [ ] I/O profiling for database/network calls
- [ ] GC pressure analysis

### Common Performance Issues
- [ ] Synchronous calls in async contexts
- [ ] Inefficient database queries (N+1 problems)
- [ ] Large object heap pressure
- [ ] Inefficient collections/LINQ usage
- [ ] Excessive string concatenations
- [ ] Unoptimized serialization

### Optimization
- [ ] Implement caching where appropriate
- [ ] Optimize database queries
- [ ] Use async patterns correctly
- [ ] Profile memory allocations
- [ ] Consider batch operations
- [ ] Optimize startup performance

### Validation
- [ ] Before/after performance comparison
- [ ] Load testing with realistic scenarios
- [ ] Memory usage verification
- [ ] User experience validation
- [ ] Monitor in production


Quick Reference Commands

Diagnostic Tools

# Memory analysis
dotnet-dump collect -p <pid>
dotnet-dump analyze dump.dmp

# Performance tracing
dotnet-trace collect -p <pid> --duration 00:00:30
dotnet-trace convert trace.nettrace

# Real-time monitoring
dotnet-counters monitor -p <pid>
dotnet-gcdump collect -p <pid>

# Process information
dotnet-trace ps
dotnet-dump ps

Visual Studio Commands

Debug โ†’ Windows โ†’ Immediate Window        # Execute code during debugging
Debug โ†’ Windows โ†’ Call Stack             # View execution path
Debug โ†’ Windows โ†’ Locals                 # Current scope variables
Tools โ†’ Diagnostic Tools                 # Memory and CPU usage
Analyze โ†’ Performance Profiler           # Detailed profiling

Tags

#debugging #dotnet #performance #memory #profiling #diagnostics #troubleshooting


This debugging guide should be updated as new .NET versions and tooling become available.