(翻譯)LearnVSXNow! #10 創建我們第一個工具集-重用程式碼

  • 2019 年 10 月 5 日
  • 筆記

我們在第6和第7篇創建的Calculate小工具窗還有很多可以改進的地方,所以在這篇文章里,我們不會開發新的功能,而是重構我們的程式碼,封裝出可以重用的類和方法。

VSX背後的對象模型是非常豐富的:有幾百個類和幾千個方法。但我們在開發VS add-in和package的時候,光記住類和方法的名字是不夠的,我們還需要知道相應的GUID以及其他相關的常數。

我覺得在VSX的開發中最難的是開發者必須要把.NET和COM混著用。如果VSX的編程模型(對象模型)更簡潔一點話,對開發人員是非常好的事情。

微軟在interop程式集之上,開發了一些用於託管程式碼的層(其中一個叫做MPF,全稱是Managed Package Framework)。我認為MPF里提供的類和方法是非常棒的,但它們只會涉及到VSX的某些方面,還不夠。

所以在這篇文章里,我會告訴你如何把常用的功能封裝出來,供我們以後開發VSX時使用。我希望你也能夠在開發過程中,逐步創建你自己需要的工具集。

從這篇文章開始,我會創建一個叫做VsxTools的類庫。這一次我僅僅出於演示目的來使用這個類庫,但是既然我們是一起學習VSX的,所以我打算把這個類庫弄成一個真正可用的工具。在這篇文章里我會做如下的重構:

  1. 改進活動日誌的調用
  2. 簡化output window的調用

CodePlex上的源碼

當你在看這篇文章的時候,我已經把所有的示例程式碼和文章放到了CodePlex上了(http://www.codeplex.com/LearnVSXNow)。如果下載了最新的源碼,你會看到在PackageStartupSamples目錄下有一個PackageStartupSamples.sln文件。它包含了這系列文章里的所有的例子。我會隨著VS 2008 SDK版本的更新來相應的更新這些例子(當然如果發現了bug的話,我也會更新它們)。

創建VsxTools類庫

我們最好把可重用的程式碼放到一個單獨的類庫里。所以,讓我們創建一個名為VsxTools的C# class library項目,並把它添加到StartupToolsetRefactored項目所在的解決方案中。由於我們需要向這個VsxTools中添加VSX程式碼,所以我們要向這個項目中添加VS SDK interop和MPF程式集引用:

Microsoft.VisualStudio.OLE.InteropMicrosoft.VisualStudio.Shell.9.0Microsoft.VisualStudio.InteropMicrosoft.VisualStudio.Interop.8.0Microsoft.VisualStudio.Interop.9.0

接下來,我們可以向這個類庫里添加功能了。

改進活動日誌的調用

如果想往活動日誌里寫日誌的話,我們需要寫差不多半打行數的程式碼,例如:

private void LogCalculation(string firstArg, string secondArg, string operation,  string result){  string message = String.Format("Calculation executed: {0} {1} {2} = {3}",    firstArg, operation, secondArg, result);  IVsActivityLog log =    Package.GetGlobalService(typeof(SVsActivityLog)) as IVsActivityLog;  if (log == null) return;    log.LogEntry(    (result == "#Error")      ?(UInt32) __ACTIVITYLOG_ENTRYTYPE.ALE_ERROR      : (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION,    "Calculation", message);}

但是這個方法有很多「噪音」:

  1. 為了使用這個服務對象,我必須記住IVsActivityLogSVsActivityLog這兩個名字。
  2. 我必須在使用它之前判斷它是不是null。
  3. 我必須知道__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION這兩個「魔術」常數。雖然從它們的名字上可以猜出它們代表的意思,但是很不直觀,而且很難記住。我甚至還得把它們轉換成System.UInt32類型。
  4. 在上面這個例子里,由於我們只需要記錄日誌類型、日誌源和日誌消息,所以我們調用了LogEntry方法。但是如果我們想記錄其它資訊,我們還得找另外一個方法才行。

所以必須得想辦法去掉這些「噪音」。如果能用下面這段程式碼豈不是很好?

string message = String.Format("Calculation executed: {0} {1} {2} = {3}", firstArg, operation, secondArg, result);ActivityLog.Write(result == "#Error" ? ActivityLogType.Error : ActivityLogType.Information,  "Calculation", message);

在這段程式碼里,我們減少了如下「噪音」:

  1. 要想使用活動日誌這個service的話,我們只需要記住一個直觀的名字ActivityLog就行了。
  2. 用一個很直觀的枚舉來代表日誌類型。
  3. 寫日誌的時候,只需要一個Write方法就夠了。當然這個方法有很多個重載版本,可以覆蓋所有的參數組合。
  4. 不需要轉換類型,不需要空引用檢查。

另外,這種非常簡單的、帶智慧感知的方式可以提高我們敲程式碼的速度。

定義ActivityLog

改進的ActivityLog模式基於3個類型:

// --- Represents entry types instead of __ACTIVITYLOG_ENTRYTYPE constantspublic enum ActivityLogType{  Information,  Warning,  Error}  // --- Represents an entity holding all log entry propertiespublic sealed class ActivityLogEntry{  ...  public ActivityLogType Type { get; set; }  public string Source { get; set; }  public string Message { get; set; }  public Guid? Guid { get; set; }  public int? Hr { get; set; }  public string Path { get; set; }  ...}  // --- Provides log services through static Write methodspublic static class ActivityLog{  public static void Write(ActivityLogEntry entry);  public static void Write(string source, string message);  ...  public static void Write(string source, string message, Guid guid, int hr);  ...  public static void Write(ActivityLogType type, string source, string message);  ...}

ActivityLogType枚舉的功能是顯而易見的,所以就不說它了。靜態類ActivityLog通過Write方法供外面調用,這個方法有很多重載版本,可以適應不同的參數組合。如果我們在編程的時候不能確定要記錄日誌的哪些屬性,可以調用接收ActivityLogEntry類型的Write方法的重載版本。在這個方法內部判斷應該調用IVsActivityLog的哪個方法,例如,如果只用到了Hr和Path屬性,我們可以調用LogEntryHrPath方法。

ActivityLog的內部實現

VsxTools項目里添加一個ActivityLog.cs文件,並在裡面添加上面的三個類型。在ActivityLogEntry類里,我弄了幾個構造函數,每一個負責設置不同的屬性。最主要的「邏輯」是寫在ActivityLog靜態類里的,在這個類里,我添加了一些私有屬性和私有方法:

public static class ActivityLog{  ...  private static UInt32 MapLogTypeToAle(ActivityLogType logType)  {    switch (logType)    {      case ActivityLogType.Information:        return (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION;      case ActivityLogType.Warning:        return (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_WARNING;      default:        return (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR;    }  }    private static IVsActivityLog Log  {    get     {       return Package.GetGlobalService(typeof (SVsActivityLog)) as IVsActivityLog;     }  }    private static void LogEntry(ActivityLogType type, string source,     string message)  {    IVsActivityLog log = Log;    if (log  != null)    {      log.LogEntry(MapLogTypeToAle(type), source, message);    }  }  ...}

這些方法都很簡單,就不解釋它們了。和LogEntry方法一樣,我還添加了IVsActivityLog服務中其他的方法,例如LogEntryGuid。在Write方法里,可以調用這些私有方法:

public static void Write(string source, string message){  Write(ActivityLogType.Information, source, message);}  public static void Write(ActivityLogType type, string source, string message){  LogEntry(type, source, message);}

就這些就行了。通過實現這個東西,我們就擁有了一個非常簡單並且容易記住的活動日誌的模型。

在舊程式碼中使用新的ActivityLog模型

現在可以修改CalculationControl.cs文件中的LogCalculation方法了:

private void LogCalculation(string firstArg, string secondArg, string operation,  string result){  string message = String.Format("Calculation executed: {0} {1} {2} = {3}", firstArg, operation, secondArg, result);  ActivityLog.Write(result == "#Error" ? ActivityLogType.Error : ActivityLogType.Information, "Calculation", message); }

現在,你可以編譯並運行一下StartupToolsetRefactored例子了。別忘了我們曾在第7章講過怎樣查看活動日誌,還有,別忘了在項目屬性的Debug頁簽里加上/log開關,這樣它才能記錄活動日誌。

瞧一瞧output window的後台結構

在這篇文章開始的時候,我說過我要簡化一下output window的使用,所以讓我們開始吧。在第7篇文章中,我們已經用IVsOutputWindowIVsOutputWindowPane介面向VS的output window寫了日誌了:

private void LogCalculationToOutput(string firstArg, string secondArg,   string operation, string result){  string message = String.Format("Calculation executed: {0} {1} {2} = {3} ", firstArg, operation, secondArg, result);    IVsOutputWindow outWindow =    Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;  Guid generalWindowGuid = VSConstants.GUID_OutWindowGeneralPane;  IVsOutputWindowPane windowPane;  outWindow.GetPane(ref generalWindowGuid, out windowPane);  windowPane.OutputString(message);}

就像活動日誌的調用方式那樣,上面紅色的程式碼也有著類似的「噪音」。但在我們去掉這些噪音之前,讓我們先來瞧一瞧VS output window的結構和與它相關的服務。

Visual Studio只有一個output window,但是它卻可以包含多個pane來隔離多種output。Visual Studio它自己定義了一些output window pane,VSPackage也可以定義他們自己的pane。一個package可以向任何已有的pane中(包括VS IDE定義的和第三方package定義的)輸出消息。下圖展示了VS IDE定義的「常規」pane和一個自定義的「My Debug」pane:

要使用output window,用到兩個簡單的服務:

  1. SVsOutputWindow服務用來管理(獲取、創建和刪除)output window pane,但它不能用來輸出消息。不過它可以獲取IVsOutputWindowPaneIVsOutputWindowPane2(第二個是第一個的擴展)的實例,這些介面用來把output資訊輸出到相應的window pane中。
  2. SVsGeneralOutputWindowPane服務用於取得General output window pane對應的IVsOutputWindowPane實例。

這些介面的功能如下:

服務介面

功能

IVsOutputWindow

這個介面只有3個方法,用來管理output window pane的實例,分別是: CreatePane, DeletePane, GetPane

IVsOutputWindow2

擴展IVsOutputWindow介面,添加了一個新的方法,用於獲取當前在用的pane的ID:GetActivePaneGUID

IVSOutputWindowPane

這個介面用於管理對應的pane的內容和可見性。 可以調用Activate 方法顯示一個pane,調用Hide方法來隱藏一個pane。每個pane都有一個名字,可以通過GetName和SetName 方法來獲取名字或設置名字。pane裡面的內容可以通過Clear、OutputString和OutputStringThreadSafe方法來管理。 發送到window pane里的資訊也可以通過調用OutputTaskItemString、 OutputTaskItemStringEx和FlushToTaskList方法來放到任務列表中。

IVsOutputWindowPane2

擴展IVsOutputWindowPane介面,添加了OutputTaskItemStringEx2方法,可以把output資訊和錯誤列表中的消息關聯起來。

總結一下上述表格:用IVsOutputWindow來管理pane,用IVsOutputWindowPane來管理每個pane中的output資訊。

window pane由GUID來標識。在Microsoft.VisualStudio.VSConstants類里,定義了3個VS IDE中常用的pane的GUID:、

Window Pane

GUID

General

GUID_OutWindowGeneralPane

Build

GUID_BuildOutputWindowPane

Debug

GUID_OutWindowDebugPane

如果一個package創建了一個window pane,必須有它自己的GUID。我們可以用這個GUID來獲取這個pane的引用,就像其他VS IDE內置的pane一樣。但如果這個package沒有公開出這個GUID的話,我們也可以用IVSOutputWindow2GetActivePaneGUID來得到這個GUID。

負責管理pane的方法

通過SVsOutputWindow得到的IVsOutputWindow介面實例有3個用於管理pane的方法:

public interface IVsOutputWindow{  int GetPane(ref Guid rguidPane, out IVsOutputWindowPane ppPane);  int CreatePane(ref Guid rguidPane, string pszPaneName, int fInitVisible, int fClearWithSolution);  int DeletePane(ref Guid rguidPane);}

每一個方法的第一個參數都是pane的GUID。這3個方法的名字已經很清楚的告訴我們它們是幹嘛的了。調用CreatePane方法的時候,你需要傳遞3個額外的參數:

  1. pszPaneName表示pane的初始名字(可以在創建後改變這個名字)
  2. fInitVisible用於設置pane的初始可見性。如果設成了true(即非0值),這個pane在創建後會立刻顯示。當然,這裡是說這個pane會顯示在output window里,但output window是可以隱藏的。不過你可以通過視圖|輸出(View|Output)菜單來顯示output window。
  3. fClearWithSolution參數如果設成true的話,pane裡面的內容就會隨著解決方案的關閉而自動清空。

你也許認為,如果我們對VS內置的output pane調用CreatePaneDeletePane的話,VS會報錯。但是不是這樣的,這兩個方法也可以刪除和重新創建原本已經內置的pane。所以在用的時候你必須意識到這一點。

最常用的方法是GetPane,它可以獲取一個IVsOutputWindowPane的實例,從而向相應的pane中寫消息。

把消息發送到pane中

IVsOutputWindowPane介面提供了往pane中寫消息的功能。你可以把文本消息輸出到pane中,也可以輸出到任務列表中,但是在這篇文章中,我僅僅把消息直接輸出到pane中(處理任務列表是以後的文章的主題)。通過調用OutputString或OutputStringThreadSafe這兩個方法,你可以用執行緒安全或執行緒不安全的形式把消息輸出到pane中。什麼時候需要用執行緒安全的方法,什麼時候不需要用,這個要搞清楚。如果你搞不清楚的話,那就用OutputStringThreadSafe吧。

簡化output window的調用

正如你看到的那樣,為了管理output pane並往裡面寫消息,我們需要寫好幾行有噪音的程式碼。現在讓我告訴你一個去掉這些噪音的解決方案。我並不認為這是最好的方案,但這肯定是一個解決方案。如果你有更好的主意,請告訴我。

是什麼方案

由於你們是開發人員,所以沒有什麼比直接看程式碼能夠說的更清楚了。我的解決方案可以通過CalculationControl.cs文件里的這幾行程式碼來描述清楚:

public partial class CalculationControl : UserControl{  ...  private void LogCalculationToOutput(string firstArg, string secondArg,     string operation, string result)  {    string message = String.Format("Calculation executed: {0} {1} {2} = {3}",      firstArg, operation, secondArg, result);      OutputWindowPane pane = OutputWindow.GetPane(typeof(MyDebugPane));    pane.WriteLine(message);  }  ...  [PaneName("My Debug")]  [InitiallyVisible(true)]  [ThreadSafe(true)]  private sealed class MyDebugPane: OutputPaneDefinition  {}  ...}

紅色的程式碼創建了一個叫做「My Debug」的output window pane ,並且用執行緒安全的形式把消息輸出進去。OutputWindow類的GetPane方法會在需要的時候創建pane。pane以一個簡單的類的形式定義,並標記上一些屬性。所有使輪子轉起來的工作被放到了後台,調用這不用關心。

如果你想往「General」這個pane中寫消息的話,上面的程式碼還可以更短:

OutputWindow.General.WriteLine(message);
這個方案的基礎結構

這個解決方案的基礎是3個類,如下:

類型

功能

OutputPaneDefinition

可以用這個類來繼承output window pane definition(OWPD)。一個OWPD類型僅僅是一個定義,在它上面可以添加這個pane的特性的attribute。 OutputWindow和OutputWindowPane用這個類的屬性去獲取這些attribute的值。

OutputWindow

這個靜態類負責管理output window pane,就像IVsOutputWindow介面那樣。這個類也提供了靜態屬性,用這些屬性可以直接訪問到VS內置的pane。同時,這個類提供了一個異常處理機制,可以把消息轉發到「Genernal」或「Debug」 pane中,甚至轉發到一個虛擬的pane中(Silent pane)。

OutputWindowPane

這個類負責把消息輸出到它對應的pane中,和IVsOutputWindowPane介面一樣(不過它不支援任務列表的處理)。它提供Write和WriteLine方法,類似System.Console類。 你可以把這個類看成IVsOutputWindowPane的包裝類(Wrapper class)。

當用OutputPaneDefinition來定義一個pane時,我們可以把這個pane弄成默認執行緒安全的。這樣的話,就會以執行緒安全的方式當向pane中輸出消息。

定義一個pane類

如果我們需要用VS的標準pane,只需要用OutputWindow類中的GeneralDebugBuild靜態屬性就行了。

不過如果我們創建VSPackage的話,我們也許需要自己的output window pane。在「傳統」方式下,我們用一個GUID來代表這個pane,但在我的方案下,我用一個繼承自OutputWindowDefinition的類來代表這個pane,這個類上可以添加關於這個pane特性的attribute。在OutputWindowDefinition的默認構造函數里,通過讀取這些attribute來設置屬性值。 下面是這個類的定義:

public abstract class OutputPaneDefinition{  protected OutputPaneDefinition();  public virtual Guid GUID { get; }  public string Name { get; }  public bool InitiallyVisible { get; }  public bool ClearWithSolution { get; }  public bool ThreadSafe { get; }  public bool IsSilent { get; internal set; }}

我們可以把一個pane定義成安靜的,也就說並沒有物理上的pane,任何輸出到這個pane上的消息都會以安靜的模式處理掉。另外,為了定義一個已經存在的pane(例如VS內置的pane或由第三方package定義的pane),我們可以重寫Guid屬性。

為了演示這些屬性的用法,讓我們看一下OutputWindow類中的「Debug」 pane和Silent pane是怎麼定義的:

public static class OutputWindow{  ...  private sealed class DebugPane : OutputPaneDefinition  {    public override Guid GUID    {      get { return VSConstants.GUID_OutWindowDebugPane; }    }  }  ...  private sealed class SilentPane : OutputPaneDefinition  {    public SilentPane()    {      IsSilent = true;    }  }  ...}

OutputWindowDefinition可以識別如下attribute:

public sealed class PaneNameAttribute: StringAttribute {...}public sealed class InitiallyVisibleAttribute: BoolAttribute {...}public sealed class ClearWithSolutionAttribute: BoolAttribute {...}public sealed class ThreadSafeAttribute: BoolAttribute {...}

為了定義一個自己的pane,可以像下面的程式碼那樣創建一個類:

[Guid("6D71C5F7-200C-4322-A264-65C78CF511AA")][PaneName("My Own Pane")][InitiallyVisible(false)][ClearWithSolution(true)][ThreadSafe(true)]private sealed class MyOwnPane: OutputPaneDefinition {}

更詳細的程式碼細節,請參考OutputWindowDefinition.cs文件。

利用OutputWindow管理pane

我參考IVsOutputWindow提供的功能,創建了OutputWindow類,並額外添加了一些小的功能。我聲明了一個OutputPaneHandling屬性,是枚舉類型的,代表當物理上的pane無法取得時,如何處理消息。這個枚舉有如下的枚舉值:

枚舉值

含義

Silent

不產生任何異常,待輸出的資訊也不發送到任何pane中。

ThrowException

拋出WindowPaneNotFoundException異常。

RedirectToGeneral

輸出資訊轉到General pane中。

RedirectToDebug

輸出資訊轉到Debug pane中。

這個類的結構如下:

public static class OutputWindow{  public static OutputPaneHandling OutputPaneHandling { get; set; }  public static OutputWindowPane General { get; }  public static OutputWindowPane Build { get; }  public static OutputWindowPane Debug { get; }  public static OutputWindowPane Silent { get; }  public static OutputWindowPane CreatePane(Type type);  public static OutputWindowPane GetPane(Type type);  public static bool DeletePane(Type type); }

CreatePaneGetPaneDeletePane方法接受一個Type類型的參數,這個類型必須繼承自WindowPaneDefinition類。代表內置的pane的類是OutputWindow類的私有嵌套類,你不能用它們的類型作為參數,所以你也不能創建或者刪除它們。CreatePane方法只能夠創建原本不存在的pane,如果這個pane已經創建了,就只會返回它的實例。調用GetPane的時候,如果某個pane不存在,GetPane方法會創建它。

具體細節,可以參考OutputWindow.cs文件。

向pane中寫消息

我在前面提到過,OutputWindowPane類實際上是IVsOutputWindowPane實例的一個包裝。我只不過在設計和實現這個包裝類的時候做了一些小改動。

IVsOutputWindowPane用兩個單獨的方法分別以執行緒安全和不安全的方式寫消息:OutputStringThreadSafeOutputString。我想隱藏這兩個方法,這樣使用者在用的時候,就不用關心該調用哪一個。在這個類裡面,我加了一個布爾屬性ThreadSafe,由它來決定該調用哪個方法。你還記得吧,WindowPaneDefinition類識別ThreadSafeAttribute,所以當創建了一個pane的實例之後,OutputWindowPaneThreadSafe屬性值會設置成WindowPaneDefinitionThreadSafeAttribute指定的初始值。

另外,IVsOutputWindowPaneGetNameSetName方法被封裝成Name屬性。

OutputWindowPane類的成員如下:

public sealed class OutputWindowPane{   internal OutputWindowPane(OutputPaneDefinition paneDef, IVsOutputWindowPane pane);  public bool ThreadSafe { get; set; }  public string Name { get; set; }  public bool IsVirtual { get; }  public void Activate();  public void Hide();  public void Clear();  public void Write(string output);  public void Write(string format, params object[] parameters);   public void Write(IFormatProvider provider, string format, params object[] parameters);  public void WriteLine(string output);  public void WriteLine(string format, params object[] parameters);   public void WriteLine(IFormatProvider provider, string format, params object[] parameters) }

構造函數應該被弄成internal的,這樣OutputWindow類就是OutputWindowPane的工廠類了,使用者沒法自己new一個實例出來。在構造函數中,需要傳入OutputPaneDefinition實例,同時也需要傳入IVsOutputWindowPane的實例。IsVirtual屬性可以用來設置這個pane到底是一個物理上的pane,還是一個虛擬的、安靜的pane。

這個類提供了一些WriteWriteLine方法,用來代替原來的OutputStringOutputStringThreadSafe方法,並模仿System.Console中的聲明方式。

上面這些方法的實現都很簡單,具體你可以參考OutputWindowPane.cs文件。

試用一下這個方案

編譯並運行StartupToolsetRefactored項目,並點擊Calculate按鈕,你會發現消息輸出到了一個叫「My Debug」的output pane中。如果你有時間的話,可以試著對程式碼做些改動(在CalculationControl類的LogCalculationToOutput方法里),並看一下相應的變化:

// --- Original code lines:OutputWindowPane pane = OutputWindow.GetPane(typeof(MyDebugPane));pane.WriteLine(message);  // --- Change 1: Writing to two panesOutputWindowPane pane = OutputWindow.GetPane(typeof(MyDebugPane));pane.WriteLine(message);OutputWindow.General.WriteLine(message);  // --- Change 2: Changing the pane nameOutputWindowPane pane = OutputWindow.GetPane(typeof(MyDebugPane));pane.Name = "My Debug (modified)"; pane.WriteLine(message);  // --- Change 3: Reflecting to an invalid paneOutputWindowPane pane = OutputWindow.GetPane(typeof(int));pane.WriteLine(message);  // --- Change 4: Throwing an exception (VS 2008 will stop!)OutputWindow.OutputPaneHandling = OutputPaneHandling.ThrowException; OutputWindowPane pane = OutputWindow.GetPane(typeof(int));pane.WriteLine(message);  // --- Change 5: Silent exception (No output will be shown)OutputWindow.OutputPaneHandling = OutputPaneHandling.Silent;OutputWindowPane pane = OutputWindow.GetPane(typeof(int));pane.WriteLine(message);  // --- Change 6: Throwing and handling exceptionOutputWindow.OutputPaneHandling = OutputPaneHandling.ThrowException;try{  OutputWindowPane pane = OutputWindow.GetPane(typeof (int));  pane.WriteLine(message);}catch (WindowPaneNotFoundException ex){  OutputWindow.General.WriteLine(ex.Message);}

總結

在這篇文章里,我們修改了StartupToolsetRefactored項目,以VsxTools的形式提供helper類。這些helper類是託管的類型,減少了由VS 2008 SDK的interop類帶來的「噪音」。我們為活動日誌和output widow pane開發了這種可重用的類。

現在,所有的源程式碼(包括前幾篇文章的例子)和文章可以在CodePlex(http://www.codeplex.com/LearnVSXNow)上找到。

我希望這些helper類能夠對你有用。但是,我寫這篇文章的本意並不是告訴你怎樣去除掉程式碼中的「噪音」,而是希望告訴你:在VS interop類的基礎上創建自己的託管類型是值得的。Microsoft在用MPF來實現這個目的,但依然還有很多地方可以使VSX的開發體驗變得更有趣和更愉快!

當開始這個系列的時候,我還沒有打算創建自己的VSX工具集,但現在我已經決定利用VSX社區的支援來做這些了…