共用方式為


建立 Visual Studio 偵錯工具視覺效果

偵錯工具視覺化檢視是 Visual Studio 功能,可在偵錯工作階段期間為特定 .NET 類型的變數或物件提供自訂視覺效果。

偵錯工具視覺化工具可以從將滑鼠移至變數並顯示的資料提示,或從自動局部變數監看式視窗中存取:

監看視窗中偵錯工具視覺化檢視的螢幕擷取畫面。

開始

請遵循 建立擴充功能專案 區段 在 快速入門 區段。

然後,新增一個類別擴展 DebuggerVisualizerProvider,並將 VisualStudioContribution 屬性套用到該類別:

/// <summary>
/// Debugger visualizer provider class for <see cref="System.String"/>.
/// </summary>
[VisualStudioContribution]
internal class StringDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
    /// <summary>
    /// Initializes a new instance of the <see cref="StringDebuggerVisualizerProvider"/> class.
    /// </summary>
    /// <param name="extension">Extension instance.</param>
    /// <param name="extensibility">Extensibility object.</param>
    public StringDebuggerVisualizerProvider(StringDebuggerVisualizerExtension extension, VisualStudioExtensibility extensibility)
        : base(extension, extensibility)
    {
    }

    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My string visualizer", typeof(string));

    /// <inheritdoc/>
    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        string targetObjectValue = await visualizerTarget.ObjectSource.RequestDataAsync<string>(jsonSerializer: null, cancellationToken);

        return new MyStringVisualizerControl(targetObjectValue);
    }
}

先前的程式碼會定義一個新的偵錯器視覺化工具,適用於類型 string 的物件。

  • 屬性 DebuggerVisualizerProviderConfiguration 會定義視覺化工具顯示名稱和支援的 .NET 類型。
  • CreateVisualizerAsync當使用者要求顯示特定值的偵錯工具視覺效果時,Visual Studio 會叫用此方法。 CreateVisualizerAsync 使用 VisualizerTarget 物件來擷取要視覺化的值,然後將其傳遞至自訂的遠端使用者控制項(請參閱 遠端 UI 文件)。 然後會傳回遠端使用者控制項,並顯示在 Visual Studio 的快顯視窗中。

鎖定多種類型

配置屬性可讓視覺化工具在方便時以多個類型為目標。 一個完美的例子是 DataSet Visualizer,它支援 DataSetDataTableDataViewDataViewManager 物件的視覺化。 這項功能可簡化延伸模組開發,因為類似的類型可以共用相同的 UI、檢視模型和 視覺化工具物件來源

    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new DebuggerVisualizerProviderConfiguration(
        new VisualizerTargetType("DataSet Visualizer", typeof(System.Data.DataSet)),
        new VisualizerTargetType("DataTable Visualizer", typeof(System.Data.DataTable)),
        new VisualizerTargetType("DataView Visualizer", typeof(System.Data.DataView)),
        new VisualizerTargetType("DataViewManager Visualizer", typeof(System.Data.DataViewManager)));

    /// <inheritdoc/>
    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        ...
    }

視覺化工具物件來源

視覺化檢視物件來源是偵錯工具在偵錯程式中載入的 .NET 類別。 偵錯工具視覺化器可以使用VisualizerTarget.ObjectSource所公開的方法,從視覺化器物件來源擷取資料。

預設視覺化檢視器物件來源可讓偵錯工具視覺化檢視藉由呼叫 RequestDataAsync<T>(JsonSerializer?, CancellationToken) 方法來擷取要視覺化的物件值。 預設視覺化檢視物件來源會使用 Newtonsoft.Json 來序列化值,而 VisualStudio.Extensibility 程式庫也會使用 Newtonsoft.Json 進行還原序列化。 或者,您可以使用 RequestDataAsync(CancellationToken) 來將序列化值作為 JToken 進行擷取。

如果您想要將 Newtonsoft.Json 原生支援的 .NET 類型視覺化,或想要視覺化自己的類型,並使其可序列化,則上述指示足以建立簡單的偵錯工具視覺效果。 如果您想支援更複雜的類型或使用更高級的功能,請繼續閱讀。

使用自訂視覺化物件來源

如果 Newtonsoft.Json 無法自動序列化要視覺化的類型,您可以建立自訂視覺化檢視器物件來源來處理序列化。

  • 建立以 netstandard2.0 為目標的新的 .NET 類別庫專案。 如有必要,您可以以更具體的 .NET Framework 或 .NET 版本為目標 (例如 或 net472net6.0),以序列化要視覺化的物件。
  • 新增 17.6 版或更新版本的 DebuggerVisualizers 套件參考。
  • 新增一個類別以繼承VisualizerObjectSource,並覆寫GetData方法,將target的序列化值寫入outgoingData流程。
public class MyObjectSource : VisualizerObjectSource
{
    /// <inheritdoc/>
    public override void GetData(object target, Stream outgoingData)
    {
        MySerializableType result = Convert(match);
        SerializeAsJson(outgoingData, result);
    }

    private static MySerializableType Convert(object target)
    {
        // Add your code here to convert target into a type serializable by Newtonsoft.Json
        ...
    }
}

使用自訂序列化

您可以使用該 VisualizerObjectSource.SerializeAsJson 方法將使用 Newtonsoft.Json 將物件序列化為 , Stream 而無需將對 Newtonsoft.Json 的引用新增至您的程式庫。 叫用SerializeAsJson 會透過反射把 Newtonsoft.Json 程式集的版本載入到正在偵錯的進程中。

如果您需要參考 Newtonsoft.Json,您應該使用套件所 Microsoft.VisualStudio.Extensibility.Sdk 參考的相同版本,但最好使用 DataContractDataMember 屬性來支援物件序列化,而不是依賴 Newtonsoft.Json 類型。

或者,您可以實作自己的自訂序列化 (例如二進位序列化) 直接寫入 outgoingData

將視覺化檢視物件來源 DLL 新增至延伸模組

修改延伸模組 .csproj 檔案,在視覺化工具物件來源程式庫專案中新增 ProjectReference,以確保在封裝延伸模組以前先建置視覺化工具物件來源程式庫。

此外,將包含視覺物件來源庫 DLL 的項目新增至延伸模組的Content子資料夾。netstandard2.0

  <ItemGroup>
    <Content Include="pathToTheObjectSourceDllBinPath\$(Configuration)\netstandard2.0\MyObjectSourceLibrary.dll" Link="netstandard2.0\MyObjectSourceLibrary.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyObjectSourceLibrary\MyObjectSourceLibrary.csproj" />
  </ItemGroup>

或者,您可以使用 net4.6.2netcoreapp 子資料夾,如果您建置以 .NET Framework 或 .NET 為目標的視覺化工具物件來源程式庫。 您甚至可以將包含不同版本視覺化工具物件來源程式庫的所有三個子資料夾加入,但最好只針對 netstandard2.0

您應該嘗試將視覺效果工具物件來源程式庫 DLL 的相依性數目降到最低。 如果您的視覺化工具物件來源程式庫具有 Microsoft.VisualStudio.DebuggerVisualizers 以外的相依性,以及已保證載入在偵錯程式中的程式庫,請務必將這些 DLL 檔案也包含在與視覺化工具物件來源程式庫 DLL 相同的子資料夾中。

更新偵錯器視覺化器提供者,以使用自訂視覺化器物件來源

然後,您可以更新 DebuggerVisualizerProvider 組態以參考自訂視覺化工具物件來源:

    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My visualizer", typeof(TypeToVisualize))
    {
        VisualizerObjectSourceType = new(typeof(MyObjectSource)),
    };

    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        MySerializableType result = await visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(jsonSerializer: null, cancellationToken);
        return new MyVisualizerUserControl(result);
    }

處理大型且複雜的物件

如果無法透過單一無參數呼叫RequestDataAsync從視覺化工具物件來源擷取資料,您可以改為呼叫多次並將不同的RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken)傳送至視覺化工具物件來源,以與視覺化工具物件來源執行更複雜的訊息交換。 訊息和回應都會由 VisualStudio.Extensibility 基礎結構使用 Newtonsoft.Json 序列化。 其他 RequestDataAsync 的覆寫方式可讓您使用 JToken 物件,或實作自訂序列化和反序列化。

您可以使用不同的訊息來實作任何自訂通訊協定,以從視覺化工具物件來源擷取資訊。 此功能最常見的使用情境是將潛在大型物件的檢索分成多個呼叫,以避免 RequestDataAsync 超時。

以下是如何逐項檢索大型集合的內容的範例:

for (int i = 0; ; i++)
{
    MySerializableType? collectionEntry = await visualizerTarget.ObjectSource.RequestDataAsync<int, MySerializableType?>(i, jsonSerializer: null, cancellationToken);
    if (collectionEntry is null)
    {
        break;
    }

    observableCollection.Add(collectionEntry);
}

上面的程式碼使用簡單的索引作為呼叫的 RequestDataAsync 訊息。 對應的視覺化工具物件原始程式碼會覆寫 TransferData 方法(而不是 GetData):

public class MyCollectionTypeObjectSource : VisualizerObjectSource
{
    public override void TransferData(object target, Stream incomingData, Stream outgoingData)
    {
        var index = (int)DeserializeFromJson(incomingData, typeof(int))!;

        if (target is MyCollectionType collection && index < collection.Count)
        {
            var result = Convert(collection[index]);
            SerializeAsJson(outgoingData, result);
        }
        else
        {
            SerializeAsJson(outgoingData, null);
        }
    }

    private static MySerializableType Convert(object target)
    {
        // Add your code here to convert target into a type serializable by Newtonsoft.Json
        ...
    }
}

上述的視覺化工具物件來源會使用方法VisualizerObjectSource.DeserializeFromJson來反序列化從incomingData發送的視覺化工具提供者的消息。

當實作一個偵錯工具視覺化提供者,並與視覺化物件來源進行複雜訊息互動時,通常最好將 VisualizerTarget 傳遞給視覺效果的RemoteUserControl,以便在載入控制項時,非同步地進行訊息交換。 透過傳遞 VisualizerTarget,您還可以將訊息傳送至視覺化物件來源,以根據使用者與視覺化工具 UI 的互動來擷取資料。

public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
    return Task.FromResult<IRemoteUserControl>(new MyVisualizerUserControl(visualizerTarget));
}
internal class MyVisualizerUserControl : RemoteUserControl
{
    private readonly VisualizerTarget visualizerTarget;

    public MyVisualizerUserControl(VisualizerTarget visualizerTarget)
        : base(new MyDataContext())
    {
        this.visualizerTarget = visualizerTarget;
    }

    public override async Task ControlLoadedAsync(CancellationToken cancellationToken)
    {
        // Start querying the VisualizerTarget here
        ...
    }
    ...

將視覺化檢視開啟為「工具視窗」

根據預設,所有偵錯程式可視化工具擴充功能在 Visual Studio 的前景開啟為模態對話視窗。 因此,如果使用者想要繼續與 IDE 互動,則需要關閉視覺化檢視。 不過,如果Style屬性在DebuggerVisualizerProviderConfiguration屬性中設定為ToolWindow,那麼視覺化檢視器將會以非模態工具視窗的形式開啟,可在偵錯會話的其餘部分持續開啟。 如果未宣告任何樣式,則會使用預設值 ModalDialog

    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My visualizer", typeof(TypeToVisualize))
    {
        Style = VisualizerStyle.ToolWindow
    };

    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        // The control will be in charge of calling the RequestDataAsync method from the visualizer object source and disposing of the visualizer target.
        return new MyVisualizerUserControl(visualizerTarget);
    }

每當視覺化檢視選擇作為ToolWindow開啟時,必須訂閱VisualizerTargetStateChanged事件。 當視覺化檢視開啟為工具視窗時,它不會封鎖使用者取消暫停偵錯會話。 因此,每當偵錯目標的狀態變更時,偵錯工具都會引發上述事件。 視覺化工具延伸模組作者應該特別注意這些通知,因為視覺化工具目標只有在偵錯工作階段處於作用中且偵錯目標暫停時才能使用。 當視覺化工具目標無法使用時,對 ObjectSource 方法的呼叫將會失敗,並產生 VisualizerTargetUnavailableException

internal class MyVisualizerUserControl : RemoteUserControl
{
    private readonly VisualizerDataContext dataContext;

#pragma warning disable CA2000 // Dispose objects before losing scope
    public MyVisualizerUserControl(VisualizerTarget visualizerTarget)
        : base(dataContext: new VisualizerDataContext(visualizerTarget))
#pragma warning restore CA2000 // Dispose objects before losing scope
    {
        this.dataContext = (VisualizerDataContext)this.DataContext!;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.dataContext.Dispose();
        }
    }

    [DataContract]
    private class VisualizerDataContext : NotifyPropertyChangedObject, IDisposable
    {
        private readonly VisualizerTarget visualizerTarget;
        private MySerializableType? _value;
        
        public VisualizerDataContext(VisualizerTarget visualizerTarget)
        {
            this.visualizerTarget = visualizerTarget;
            visualizerTarget.StateChanged += this.OnStateChangedAsync;
        }

        [DataMember]
        public MySerializableType? Value
        {
            get => this._value;
            set => this.SetProperty(ref this._value, value);
        }

        public void Dispose()
        {
            this.visualizerTarget.Dispose();
        }

        private async Task OnStateChangedAsync(object? sender, VisualizerTargetStateNotification args)
        {
            switch (args)
            {
                case VisualizerTargetStateNotification.Available:
                case VisualizerTargetStateNotification.ValueUpdated:
                    Value = await visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(jsonSerializer: null, CancellationToken.None);
                    break;
                case VisualizerTargetStateNotification.Unavailable:
                    Value = null;
                    break;
                default:
                    throw new NotSupportedException("Unexpected visualizer target state notification");
            }
        }
    }
}

Available建立之後,以及在新建的視覺化工具視窗中RemoteUserControl顯示之前,將收到通知。 只要視覺效果保持開啟狀態,每次偵錯目標變更其狀態時,都可以接收其他 VisualizerTargetStateNotification 值。 通知 ValueUpdated 用來指出可視化工具所開啟的最後一個運算式已在偵錯工具停止運行的位置成功重新評估,並應由 UI 來更新。 另一方面,每當調試目標恢復運行,或是在停止後表達式無法重新評估時,將會收到 Unavailable 通知。

更新視覺化物件值

如果VisualizerTarget.IsTargetReplaceable為 true,偵錯視覺工具可以使用ReplaceTargetObjectAsync方法來更新正在被偵錯的流程中視覺化物件的值。

可視化物件來源必須重寫 CreateReplacementObject 方法:

public override object CreateReplacementObject(object target, Stream incomingData)
{
    // Use DeserializeFromJson to read from incomingData
    // the new value of the object being visualized
    ...
    return newValue;
}

試用 RegexMatchDebugVisualizer 範例,看看這些技術的實際應用。