Visual Studio Debugger

デバッグ.NETC#C++JavaScriptVisual StudioMicrosoftWindowsIDE

デバッグツール

Visual Studio Debugger

概要

Visual Studio Debuggerは、Microsoft Visual Studioの統合デバッガーです。.NET、C++、JavaScript等に対応し、ブレークポイント、ウォッチ、コールスタック分析などの強力な機能を提供します。

詳細

Visual Studio Debuggerは、1997年のVisual Studio 97から継続的に発展してきたMicrosoftの統合開発環境(IDE)に組み込まれたデバッグシステムです。.NET Framework、.NET Core、.NET 5+、C++、JavaScript、TypeScript、Python、Node.jsなど、幅広い言語とプラットフォームをサポートし、Windows開発環境における標準的なデバッガーとしての地位を確立しています。

最大の特徴は、Visual Studioエディターとの完璧な統合による直感的なデバッグ体験です。ソースコードエディター内で直接ブレークポイントを設定し、変数の値をホバーで確認、インラインでの値表示、コードレンズによる参照情報表示など、コーディングとデバッグをシームレスに行えます。IntelliSenseとの統合により、デバッグ中でも強力なオートコンプリートとコード分析機能を利用できます。

Windows開発において特に強力なのは、Win32 API、COM、WinRT、UWPなどのWindows固有技術のデバッグサポートです。マネージドコードとネイティブコードの混在(Mixed Mode)デバッグ、SQL Server統合によるストアドプロシージャデバッグ、Azure統合によるクラウドアプリケーションデバッグなど、Microsoftエコシステム全体をカバーします。

.NET Coreのクロスプラットフォーム対応により、macOSやLinuxでも利用可能になり、Visual Studio for Mac、Visual Studio Codeでも同等の機能を提供しています。Docker環境、Kubernetes、Azure Container Instancesでのデバッグサポートも充実し、現代的なクラウドネイティブ開発にも対応しています。

2024年現在では、.NET 8、C# 12、Visual Studio 2022の最新機能として、Hot Reload、Edit and Continue、AI支援による例外分析、パフォーマンス プロファイラーとの統合などが追加され、開発者生産性の向上に大きく貢献しています。

メリット・デメリット

メリット

  • IDE完全統合: エディターとの完璧な統合による直感的操作
  • 幅広い言語サポート: .NET、C++、JavaScript、Python等の多言語対応
  • 強力なVisual機能: データ視覚化、グラフィカルなコールスタック表示
  • Microsoftエコシステム: Azure、SQL Server、Office等との統合
  • 混在モードデバッグ: マネージドとネイティブコードの同時デバッグ
  • Hot Reload: コード変更の即座反映
  • 豊富なウィンドウ: Watch、Locals、Call Stack等の専用ウィンドウ
  • エンタープライズ機能: Team Foundation Server、DevOps統合

デメリット

  • Windowsメイン: 主にWindows環境に特化
  • 重いリソース: 高いメモリとCPU使用量
  • ライセンス費用: Professional/Enterprise版は有料
  • 学習コスト: 豊富な機能による初心者の習得困難
  • 起動時間: 大規模IDEのため起動が遅い
  • プラットフォーム制限: 一部機能はWindows限定
  • バージョン依存: .NETバージョンとの互換性要件

主要リンク

書き方の例

基本的なデバッグ設定と開始

// Program.cs - デバッグ対象のC#アプリケーション
using System;
using System.Collections.Generic;
using System.Linq;

namespace DebuggingExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Visual Studio Debuggerサンプル");
            
            // ここにブレークポイント設定(F9 または 行番号クリック)
            var numbers = new List<int> { 1, 2, 3, 4, 5 };
            
            var result = ProcessNumbers(numbers);
            Console.WriteLine($"処理結果: {result}");
            
            // 例外発生コード(デバッグ対象)
            try
            {
                var divisionResult = DivideNumbers(10, 0);
                Console.WriteLine($"除算結果: {divisionResult}");
            }
            catch (Exception ex)
            {
                // 例外発生時のデバッグ
                Console.WriteLine($"エラー: {ex.Message}");
            }
            
            Console.ReadLine();
        }
        
        static int ProcessNumbers(List<int> numbers)
        {
            int sum = 0;
            
            // Watch ウィンドウで監視対象変数
            foreach (var number in numbers)
            {
                sum += number * 2;  // ステップイン対象
            }
            
            return sum;
        }
        
        static double DivideNumbers(double a, double b)
        {
            // 条件付きブレークポイント設定可能
            // 条件: b == 0
            if (b == 0)
                throw new DivideByZeroException("0で除算はできません");
            
            return a / b;
        }
    }
}

/* デバッグ実行方法:
 * 1. F5 (デバッグ開始)
 * 2. Ctrl+F5 (デバッグなしで実行)
 * 3. F9 (ブレークポイント設定/解除)
 * 4. F10 (ステップオーバー)
 * 5. F11 (ステップイン)
 * 6. Shift+F11 (ステップアウト)
 */

高度なブレークポイント機能

// AdvancedBreakpoints.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;

public class AdvancedBreakpoints
{
    public void DemonstrateBreakpoints()
    {
        var data = GenerateTestData();
        
        for (int i = 0; i < data.Count; i++)
        {
            // 条件付きブレークポイント例
            // 右クリック → 条件... → i > 5
            ProcessItem(data[i], i);
        }
    }
    
    private List<string> GenerateTestData()
    {
        return new List<string>
        {
            "Apple", "Banana", "Cherry", "Date", "Elderberry",
            "Fig", "Grape", "Honeydew", "Kiwi", "Lemon"
        };
    }
    
    private void ProcessItem(string item, int index)
    {
        // ヒットカウント ブレークポイント
        // 右クリック → 条件... → ヒットカウント → 3回目
        
        Debug.WriteLine($"Processing item {index}: {item}");
        
        // トレースポイント(ログ出力のみ、停止しない)
        // 右クリック → アクション... → メッセージをログに出力
        // メッセージ: "Item: {item}, Index: {index}"
        
        if (item.Length > 5)
        {
            // フィルター付きブレークポイント
            // 右クリック → 条件... → フィルター → ThreadId == 1
            ProcessLongName(item);
        }
    }
    
    private void ProcessLongName(string name)
    {
        // 関数ブレークポイント
        // デバッグ → 新しいブレークポイント → 関数ブレークポイント
        // 関数名: AdvancedBreakpoints.ProcessLongName
        
        var reversedName = new string(name.Reverse().ToArray());
        Console.WriteLine($"Reversed: {reversedName}");
    }
}

// デバッグ時の便利なキーボードショートカット:
// F9: ブレークポイント設定/解除
// Ctrl+Shift+F9: 全ブレークポイント削除
// Ctrl+B: ブレークポイント ウィンドウ表示
// F5: 続行
// Shift+F5: デバッグ停止
// Ctrl+Shift+F5: 再開(リスタート)

ウォッチとローカル変数の監視

// WatchExample.cs
using System;
using System.Collections.Generic;
using System.Linq;

public class WatchExample
{
    private List<Customer> customers;
    private decimal totalRevenue;
    
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Balance { get; set; }
        public DateTime LastLogin { get; set; }
        public bool IsActive { get; set; }
    }
    
    public void AnalyzeCustomers()
    {
        InitializeCustomers();
        
        // ウォッチ ウィンドウで監視する式の例:
        // customers.Count
        // customers.Where(c => c.IsActive).Count()
        // totalRevenue
        // customers.Average(c => c.Balance)
        
        foreach (var customer in customers)
        {
            // ローカル ウィンドウで 'customer' の詳細確認
            if (customer.IsActive)
            {
                totalRevenue += customer.Balance;
                
                // クイック ウォッチ (Shift+F9) で式を評価
                var daysSinceLogin = (DateTime.Now - customer.LastLogin).Days;
                
                if (daysSinceLogin > 30)
                {
                    // 自動変数ウィンドウで現在のスコープの変数確認
                    SendReactivationEmail(customer);
                }
            }
        }
        
        // イミディエイト ウィンドウでの対話的デバッグ
        // Ctrl+Alt+I でイミディエイト ウィンドウ表示
        // 例: ? customers.Count()
        // 例: ? totalRevenue.ToString("C")
        
        GenerateReport();
    }
    
    private void InitializeCustomers()
    {
        customers = new List<Customer>
        {
            new Customer { Id = 1, Name = "Alice", Balance = 1000.00m, 
                          LastLogin = DateTime.Now.AddDays(-10), IsActive = true },
            new Customer { Id = 2, Name = "Bob", Balance = 2500.50m, 
                          LastLogin = DateTime.Now.AddDays(-45), IsActive = true },
            new Customer { Id = 3, Name = "Charlie", Balance = 500.75m, 
                          LastLogin = DateTime.Now.AddDays(-60), IsActive = false }
        };
    }
    
    private void SendReactivationEmail(Customer customer)
    {
        // デバッガー表示属性の使用例
        [DebuggerDisplay("Email sent to {customer.Name} ({customer.Id})")]
        void LogEmailSent()
        {
            Console.WriteLine($"Reactivation email sent to {customer.Name}");
        }
        
        LogEmailSent();
    }
    
    private void GenerateReport()
    {
        var activeCustomers = customers.Count(c => c.IsActive);
        var averageBalance = customers.Where(c => c.IsActive).Average(c => c.Balance);
        
        // デバッガー変数ウィンドウのカスタム表示
        [DebuggerDisplay("Active: {activeCustomers}, Avg Balance: {averageBalance:C}")]
        var report = new
        {
            ActiveCustomers = activeCustomers,
            TotalRevenue = totalRevenue,
            AverageBalance = averageBalance
        };
        
        Console.WriteLine($"Report: {report}");
    }
}

例外処理とデバッグ設定

// ExceptionHandling.cs
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

public class ExceptionHandling
{
    public async Task DemonstrateExceptionDebugging()
    {
        // 例外設定: デバッグ → ウィンドウ → 例外設定
        // Common Language Runtime Exceptions → 
        //   - System.DivideByZeroException (スローされたときに中断)
        //   - System.FileNotFoundException (ユーザーが処理しない場合に中断)
        
        try
        {
            await ProcessFileOperations();
        }
        catch (FileNotFoundException ex)
        {
            // 例外発生時の詳細情報確認
            // - Inner Exception
            // - Stack Trace
            // - Data Collection
            Console.WriteLine($"ファイルエラー: {ex.Message}");
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"ネットワークエラー: {ex.Message}");
        }
        catch (Exception ex)
        {
            // 例外ヘルパーで詳細分析
            Console.WriteLine($"予期しないエラー: {ex.Message}");
        }
    }
    
    private async Task ProcessFileOperations()
    {
        // ファイル操作での例外デバッグ
        var filePath = @"C:\NonExistentFile.txt";
        
        // 第一回例外でブレーク設定時にここで停止
        var content = await File.ReadAllTextAsync(filePath);
        
        // HTTP 操作での例外デバッグ
        using var client = new HttpClient();
        client.Timeout = TimeSpan.FromSeconds(1);  // 意図的にタイムアウト設定
        
        var response = await client.GetAsync("https://httpstat.us/500");
        var responseContent = await response.Content.ReadAsStringAsync();
    }
    
    // カスタム例外の作成とデバッグ
    public class BusinessLogicException : Exception
    {
        public string ErrorCode { get; }
        public object Context { get; }
        
        public BusinessLogicException(string message, string errorCode, object context = null) 
            : base(message)
        {
            ErrorCode = errorCode;
            Context = context;
        }
    }
    
    public void BusinessProcess(int value)
    {
        if (value < 0)
        {
            // カスタム例外のデバッグ情報
            throw new BusinessLogicException(
                "負の値は許可されていません", 
                "NEGATIVE_VALUE",
                new { InputValue = value, Timestamp = DateTime.Now }
            );
        }
        
        // ビジネスロジックの処理...
    }
}

非同期・並行処理のデバッグ

// AsyncDebugging.cs
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class AsyncDebugging
{
    public async Task DemonstrateAsyncDebugging()
    {
        // 並列タスクウィンドウ: デバッグ → ウィンドウ → 並列タスク
        // 並列スタックウィンドウ: デバッグ → ウィンドウ → 並列スタック
        
        var tasks = new List<Task>();
        
        for (int i = 0; i < 5; i++)
        {
            int taskId = i;  // クロージャキャプチャ
            tasks.Add(ProcessDataAsync(taskId));
        }
        
        // 全タスクの完了待機
        await Task.WhenAll(tasks);
        
        // デッドロックの例(デバッグで確認)
        try
        {
            await DemonstrateDeadlock();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"デッドロック例外: {ex.Message}");
        }
    }
    
    private async Task ProcessDataAsync(int taskId)
    {
        // 並列タスクウィンドウでタスク状態確認
        Console.WriteLine($"Task {taskId} started on thread {Thread.CurrentThread.ManagedThreadId}");
        
        // 非同期操作のシミュレーション
        await Task.Delay(1000);
        
        // CPU集約的処理
        var result = await Task.Run(() => CalculateHeavyOperation(taskId));
        
        Console.WriteLine($"Task {taskId} completed with result: {result}");
    }
    
    private int CalculateHeavyOperation(int input)
    {
        // 呼び出し履歴ウィンドウで非同期コールスタック確認
        int result = 0;
        for (int i = 0; i < 1000000; i++)
        {
            result += (input * i) % 1000;
        }
        return result;
    }
    
    // デッドロック検出の例
    private readonly object lockA = new object();
    private readonly object lockB = new object();
    
    private async Task DemonstrateDeadlock()
    {
        var task1 = Task.Run(() =>
        {
            lock (lockA)
            {
                Thread.Sleep(100);
                lock (lockB)
                {
                    Console.WriteLine("Task 1 completed");
                }
            }
        });
        
        var task2 = Task.Run(() =>
        {
            lock (lockB)
            {
                Thread.Sleep(100);
                lock (lockA)
                {
                    Console.WriteLine("Task 2 completed");
                }
            }
        });
        
        // デッドロック発生時は並列スタックウィンドウで確認
        await Task.WhenAll(task1, task2);
    }
    
    // async/await での例外伝播デバッグ
    public async Task ChainedAsyncCalls()
    {
        try
        {
            await Level1Async();
        }
        catch (Exception ex)
        {
            // 非同期コールスタックの確認
            // - AggregateException の内部例外
            // - 元の例外発生箇所の特定
            Console.WriteLine($"Caught exception: {ex.Message}");
        }
    }
    
    private async Task Level1Async()
    {
        await Level2Async();
    }
    
    private async Task Level2Async()
    {
        await Level3Async();
    }
    
    private async Task Level3Async()
    {
        // ここで例外発生
        await Task.Delay(100);
        throw new InvalidOperationException("Level 3 で例外が発生しました");
    }
}

パフォーマンス プロファイラーとの統合

// PerformanceProfiling.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

public class PerformanceProfiling
{
    // 診断ツール: デバッグ → ウィンドウ → 診断ツール表示
    // CPU使用率、メモリ使用量をリアルタイム監視
    
    public void DemonstratePerformanceProfiling()
    {
        // メモリ使用量スナップショット取得ポイント
        var stopwatch = Stopwatch.StartNew();
        
        // CPU集約的処理
        PerformCpuIntensiveTask();
        
        stopwatch.Stop();
        Console.WriteLine($"CPU集約的処理: {stopwatch.ElapsedMilliseconds}ms");
        
        // メモリ集約的処理
        stopwatch.Restart();
        PerformMemoryIntensiveTask();
        stopwatch.Stop();
        Console.WriteLine($"メモリ集約的処理: {stopwatch.ElapsedMilliseconds}ms");
        
        // ガベージコレクション強制実行(メモリプロファイリング用)
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
    
    private void PerformCpuIntensiveTask()
    {
        // CPU使用率監視対象
        var result = 0.0;
        for (int i = 0; i < 10000000; i++)
        {
            result += Math.Sqrt(i) * Math.Sin(i);
        }
        Console.WriteLine($"CPU計算結果: {result:F2}");
    }
    
    private void PerformMemoryIntensiveTask()
    {
        // ヒープメモリ使用量監視対象
        var data = new List<byte[]>();
        
        for (int i = 0; i < 1000; i++)
        {
            // 大きなバイト配列を作成(メモリプレッシャー)
            var largeArray = new byte[1024 * 1024];  // 1MB
            
            // パターンで埋める
            for (int j = 0; j < largeArray.Length; j += 1024)
            {
                largeArray[j] = (byte)(i % 256);
            }
            
            data.Add(largeArray);
        }
        
        Console.WriteLine($"作成した配列数: {data.Count}");
        
        // メモリ使用量をプロファイラーで確認
        var totalMemory = GC.GetTotalMemory(false);
        Console.WriteLine($"現在のメモリ使用量: {totalMemory / 1024 / 1024} MB");
    }
    
    // ETW (Event Tracing for Windows) イベント
    [Conditional("DEBUG")]
    public void LogPerformanceEvent(string eventName, object data)
    {
        // パフォーマンス カウンターでの計測
        Debug.WriteLine($"[PERF] {eventName}: {data}");
    }
}

Visual Studio 設定とカスタマイズ

// DebuggerCustomization.cs
using System;
using System.Diagnostics;

// デバッガー表示属性の活用
[DebuggerDisplay("User: {Name} (ID: {Id}, Active: {IsActive})")]
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public string InternalSecret { get; set; }  // デバッガーで非表示
    
    [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
    public Dictionary<string, object> Properties { get; set; }  // 折りたたみ表示
}

// デバッガー型プロキシ
[DebuggerTypeProxy(typeof(UserCollectionDebugView))]
public class UserCollection : List<User>
{
    public int ActiveUserCount => this.Count(u => u.IsActive);
}

// カスタムデバッガー表示
internal class UserCollectionDebugView
{
    private readonly UserCollection collection;
    
    public UserCollectionDebugView(UserCollection collection)
    {
        this.collection = collection;
    }
    
    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    public User[] Users => collection.ToArray();
    
    public int TotalUsers => collection.Count;
    public int ActiveUsers => collection.ActiveUserCount;
}

/*
Visual Studio デバッガー設定のカスタマイズ:

1. ツール → オプション → デバッグ → 全般
   - "すべてのモジュールに対してJust My Codeを有効にする"
   - "ソース サーバー サポートを有効にする"
   - "Edit and Continue を有効にする"

2. ツール → オプション → デバッグ → 出力ウィンドウ
   - プログラム出力、例外メッセージの表示設定

3. ツール → オプション → デバッグ → シンボル
   - シンボルファイルの場所とキャッシュ設定

4. ブレークポイントのエクスポート/インポート
   - デバッグ → ウィンドウ → ブレークポイント → エクスポート

5. カスタム視覚化ツール(.natvis ファイル)
   - 複雑なデータ構造の表示カスタマイズ
*/