寫在前面:假定你在日常的工作中使用到了Visual Studio,並期望了解一些調試技巧來提高工作效率,也許本文適合你。以下Visual Studio簡稱vs。
一、入門
以最簡單的控制台應用程序為例,代碼如下:
1 class Program
2 { 3 static void Main(string[] args) 4 { 5 int result = Sum(2, 3); 6 Console.WriteLine("2+3={0}", result); 7 } 8 9 private static int Sum(int a,int b) 10 { 11 return a + b; 12 } 13 }
調試的根本目的是跟蹤代碼、程序的狀態,判斷是否按照期望的行為運行。常用的跟蹤手段有控制台輸出、日志輸出以及斷點調試。
- 控制台輸出用於開發環境,可以在vs輸出窗口中查看程序輸出的內容如下圖所示:
由於是控制台應用程序,Console.WriteLine() 輸出的內容不會顯示在輸出窗口,故采用Trace.WriteLine() 。對非控制台應用程序,Console.WriteLine() 輸出的內容會正常顯示在輸出窗口。
2. 日志輸出用於開發環境和生產環境,但更多用於生產環境,用來收集程序的運行信息。常用的日志組件有Log4Net、NLog以及自定義日志組件。依據問題嚴重程度大致分為嚴重錯誤、錯誤、警告、信息以及調試信息等幾個級別。可結合實際需求靈活配置。
3. 斷點調試多用於開發環境,通過設置斷點,讓程序在指定的位置暫停,以便觀察上下文環境情況。
以上圖為例,添加斷點后,鼠標移動到變量名上,可以觀察一些變量的值。對於復雜類型的變量,通過選中變量,右鍵選擇快速監視的方式。避免鼠標移動后,監視的信息消失。
以上三種調試方法中,對於開發環境而言,使用最為頻繁的方法當數斷點調試。后面以斷點調試為主,深入介紹。
二、進階
-
啟動外部程序
要使用斷點調試,需要滿足一些斷點調試的條件。對於可執行程序,如控制台應用程序、窗體應用程序、WPF應用程序以及Web應用程序,啟動調試后,可以在期望的位置添加斷點。而對於如動態庫類型,不可以直接啟動調試。想要調試這類項目,有兩種方式。一種是可以設置項目屬性中的啟動操作,指定引用該動態庫的可執行程序路徑。
另一種方式是運行調用了動態庫的可執行程序,通過附加可執行程序進程的方式來調試。
-
附加進程
新建 DllDemo 動態庫項目,添加 MyMath 類,添加靜態方法 Max(int a,int b) 。代碼如下:
1 using System;
2
3 namespace DllDemo 4 { 5 public class MyMath 6 { 7 public static int Max(int a,int b) 8 { 9 return Math.Max(a, b); 10 } 11 } 12 }
添加對 DllDemo 動態庫項目引用, 並修改控制台應用程序如下。為了方便后續調試,控制台應用程序中添加 Console.Read()。
1 using DllDemo;
2 using System; 3 using System.Diagnostics; 4 5 namespace DebugDemo 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Console.WriteLine("等待鍵盤輸入..."); 12 13 Console.Read(); 14 15 int result = Sum(2, 3); 16 17 Console.WriteLine(string.Format("2+3={0}", result)); 18 19 result = MyMath.Max(2, 3); 20 21 Console.WriteLine(string.Format("MyMath.Max(2, 3)={0}", result)); 22 } 23 24 private static int Sum(int a, int b) 25 { 26 return a + b; 27 } 28 } 29 }
運行控制台應用程序DebugDemo.exe ,附加該進程,在合適的位置添加斷點。
- 條件斷點
在多層循環中,有時想要滿足一定條件時命中斷點。這時,條件斷點會比較有效。以下面代碼為例,想要index = 10(number > 10)時命中斷點。
private int Sum(int number) { int result = 0; for(int index= 0;index<number;index++) { result += index; } return result; }
在合適的位置,按下F9設置斷點。右鍵紅色的斷點,選擇條件...,在條件中輸入 index == 10 然后關閉。運行程序進入循環體后,會在index = 10時,命中斷點
- 即時窗口
有時,當程序運行起來后,進入了某種上下文環境中。此時,想要跟蹤程序在另一種上下文環境的運行情況,可以在即時窗口中直接設置某些上下文環境。代碼如下所示:
internal class People { public string Name { get; set; } public bool IsMale { get; set; } } static void Main(string[] args) { PrintSex(new People() { Name = "張三",IsMale =true}); } private static void PrintSex(People people) { if (people.IsMale) { Console.WriteLine("{0} is 男性", people.Name); } else { Console.WriteLine("{0} is 女性", people.Name); } }
程序運行以后,會進入people為男性的分支。在 if (people.IsMale) 行設置斷點,當進入斷點以后,在即時窗口中更改 people.IsMale 的值后按下Enter鍵執行,使程序進入另一個分支。
- 更改執行順序
以即時窗口涉及到的代碼為例,無論上下文環境如何,想要直接進入 people.IsMale 的分支,可在if (people.IsMale) 行設置斷點。進入斷點后,在紅色斷點處。直接按住鼠標左鍵,拖動到 Console.WriteLine("{0} is 女性", people.Name); 行,進入該分支。
-
查看調用堆棧
當程序包含接口繼承、抽象類繼承等邏輯,導致結構過於復雜,知道功能入口以及出口,想要了解過程時,調用堆棧會比較有用。以下面代碼為例:
private static void DoWork()
{
DoWork1();
}
private static void DoWork1() { DoWork2(); } private static void DoWork2() { DoWork3(); } private static void DoWork3() { Console.Write("DoWork3"); }
假設知道功能入口為DoWork,功能結果為DoWork3,想要了解DoWork3的調用邏輯,可以在DoWork3中設置斷點,啟動調試后打開調用堆棧窗口,如下圖:
-
異常設置
當程序運行以后,結果不是預期的。初步猜測發生了異常,由於某些原因,捕獲了異常,卻未妥善處理,導致異常信息被“吞”掉。此時,異常設置會格外有效。以下面代碼為例:
private static void TryToDivideByZero()
{
try { int a = 9; int b = 0; int c = a / b; } catch(Exception ex) { Console.WriteLine(ex.Message); } }
由於方法中存在異常,又有異常捕獲,后續邏輯會被打斷,此時對異常設置做如下設置:
重新調試程序會有如下結果,方便快速定位異常發生點。
三、高級
在某些場景下,開發環境運行正常,非開發環境運行異常,依賴常規手段無法定位問題原因,想要斷點調試,非開發環境運行缺少VS時,遠程調試會比較有效。
在VS安裝目錄下拷貝遠程調試所需的文件夾x86,x64到非開發環境
依據遠程目標機系統環境,運行x86/x64文件夾下msvsmon.exe,選擇工具中的選項菜單做如下配置:
運行待調試程序后,在VS中選擇調試>附加到進程(ctrl+alt+p),設置連接類型,連接目標(遠程ip地址或計算機名)后查找,會自動列出相關內容。
在可用進程中選擇對應的進程
在合適的位置添加斷點即可開始調試了