Bagikan melalui


Membuat visualizer debugger Visual Studio

Visualizer debugger adalah fitur Visual Studio yang menyediakan visualisasi kustom untuk variabel atau objek dari jenis .NET tertentu selama sesi debug.

Visualizer debugger dapat diakses dari Tip Data yang muncul saat mengarahkan mouse ke atas variabel, atau dari jendela Otomatis, Lokal, dan Tonton :

Screenshot of debugger visualizers in the watch window.

Mulai

Ikuti bagian Buat proyek ekstensi di bagian Memulai.

Kemudian, tambahkan kelas yang diperluas DebuggerVisualizerProvider dan terapkan VisualStudioContribution atribut ke dalamnya:

/// <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);
    }
}

Kode sebelumnya mendefinisikan visualizer debugger baru, yang berlaku untuk objek jenis string:

  • Properti DebuggerVisualizerProviderConfiguration menentukan nama tampilan visualizer dan jenis .NET yang didukung.
  • Metode CreateVisualizerAsync ini dipanggil oleh Visual Studio ketika pengguna meminta tampilan visualizer debugger untuk nilai tertentu. CreateVisualizerAsync menggunakan objek untuk mengambil nilai yang akan divisualisasikan VisualizerTarget dan meneruskannya ke kontrol pengguna jarak jauh kustom (referensi dokumentasi Antarmuka Pengguna Jarak Jauh). Kontrol pengguna jarak jauh kemudian dikembalikan dan akan ditampilkan di jendela popup di Visual Studio.

Menargetkan beberapa jenis

Properti konfigurasi memungkinkan visualizer untuk menargetkan beberapa jenis ketika nyaman. Contoh sempurna dari ini adalah Visualizer Himpunan Data yang mendukung visualisasi DataSetobjek , , DataTableDataView, dan DataViewManager . Kemampuan ini memudahkan pengembangan ekstensi karena jenis serupa dapat berbagi UI, model tampilan, dan sumber objek visualizer yang sama.

    /// <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)
    {
        ...
    }

Sumber objek visualizer

Sumber objek visualizer adalah kelas .NET yang dimuat oleh debugger dalam proses yang sedang di-debug. Visualizer debugger dapat mengambil data dari sumber objek visualizer menggunakan metode yang diekspos oleh VisualizerTarget.ObjectSource.

Sumber objek visualizer default memungkinkan visualizer debugger untuk mengambil nilai objek yang akan divisualisasikan dengan memanggil RequestDataAsync<T>(JsonSerializer?, CancellationToken) metode . Sumber objek visualizer default menggunakan Newtonsoft.Json untuk menserialisasikan nilai, dan pustaka VisualStudio.Extensibility juga menggunakan Newtonsoft.Json untuk deserialisasi. Atau Anda dapat menggunakan RequestDataAsync(CancellationToken) untuk mengambil nilai serial sebagai JToken.

Jika Anda ingin memvisualisasikan jenis .NET yang didukung secara asli oleh Newtonsoft.Json, atau Anda ingin memvisualisasikan jenis Anda sendiri dan Anda dapat membuatnya dapat diserialisasikan, instruksi sebelumnya cukup untuk membuat visualizer debugger sederhana. Baca apakah Anda ingin mendukung jenis yang lebih kompleks atau menggunakan fitur yang lebih canggih.

Menggunakan sumber objek visualizer kustom

Jika jenis yang akan divisualisasikan tidak dapat diserialisasikan secara otomatis oleh Newtonsoft.Json, Anda dapat membuat sumber objek visualizer kustom untuk menangani serialisasi.

  • Buat proyek pustaka kelas .NET baru yang menargetkan netstandard2.0. Anda dapat menargetkan versi .NET Framework atau .NET yang lebih spesifik (misalnya, net472 atau net6.0) jika perlu untuk menserialisasikan objek yang akan divisualisasikan.
  • Tambahkan referensi paket ke DebuggerVisualizers versi 17.6 atau yang lebih baru.
  • Tambahkan kelas yang diperluas VisualizerObjectSource dan ambil alih GetData penulisan target nilai serial ke outgoingData aliran.
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
        ...
    }
}

Menggunakan serialisasi kustom

Anda dapat menggunakan VisualizerObjectSource.SerializeAsJson metode untuk membuat serial objek menggunakan Newtonsoft.Json ke Stream tanpa menambahkan referensi ke Newtonsoft.Json ke pustaka Anda. Pemanggilan SerializeAsJson akan dimuat, melalui pantulan, versi rakitan Newtonsoft.Json ke dalam proses yang sedang di-debug.

Jika Anda perlu mereferensikan Newtonsoft.Json, Anda harus menggunakan versi yang sama yang direferensikan oleh Microsoft.VisualStudio.Extensibility.Sdk paket, tetapi lebih baik menggunakan DataContract atribut dan DataMember untuk mendukung serialisasi objek alih-alih mengandalkan jenis Newtonsoft.Json.

Atau, Anda dapat menerapkan serialisasi kustom Anda sendiri (seperti serialisasi biner) menulis langsung ke outgoingData.

Menambahkan DLL sumber objek visualizer ke ekstensi

Ubah file ekstensi .csproj yang ProjectReference menambahkan ke proyek pustaka sumber objek visualizer, yang memastikan bahwa pustaka sumber objek visualizer dibangun sebelum ekstensi dimas.

Content Tambahkan juga item termasuk DLL pustaka sumber objek visualizer ke netstandard2.0 dalam subfolder ekstensi.

  <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>

Atau, Anda dapat menggunakan net4.6.2 subfolder atau netcoreapp jika Anda membangun pustaka sumber objek visualizer yang menargetkan .NET Framework atau .NET. Anda bahkan dapat menyertakan ketiga subfolder dengan versi pustaka sumber objek visualizer yang berbeda, tetapi lebih baik untuk menargetkan netstandard2.0 saja.

Anda harus mencoba meminimalkan jumlah dependensi DLL pustaka sumber objek visualizer. Jika pustaka sumber objek visualizer Anda memiliki dependensi selain Microsoft.VisualStudio.DebuggerVisualizers dan pustaka yang sudah dijamin dimuat dalam proses yang sedang di-debug, pastikan juga untuk menyertakan file DLL tersebut ke dalam subfolder yang sama dengan DLL pustaka sumber objek visualizer.

Memperbarui penyedia visualizer debugger untuk menggunakan sumber objek visualizer kustom

Anda kemudian dapat memperbarui konfigurasi untuk DebuggerVisualizerProvider mereferensikan sumber objek visualizer kustom Anda:

    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);
    }

Bekerja dengan objek besar dan kompleks

Jika pengambilan data dari sumber objek visualizer tidak dapat dilakukan dengan satu panggilan tanpa parameter ke RequestDataAsync, Anda dapat melakukan pertukaran pesan yang lebih kompleks dengan sumber objek visualizer dengan memanggil RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) beberapa kali dan mengirim pesan yang berbeda ke sumber objek visualizer. Baik pesan maupun respons diserialisasikan oleh infrastruktur VisualStudio.Extensibility menggunakan Newtonsoft.Json. Penimpaan RequestDataAsync lain memungkinkan Anda menggunakan JToken objek atau menerapkan serialisasi dan deserialisasi kustom.

Anda dapat menerapkan protokol kustom apa pun menggunakan pesan yang berbeda untuk mengambil informasi dari sumber objek visualizer. Kasus penggunaan yang paling umum untuk fitur ini adalah memecah pengambilan objek yang berpotensi besar menjadi beberapa panggilan untuk menghindari RequestDataAsync waktu habis.

Ini adalah contoh bagaimana Anda dapat mengambil konten koleksi yang berpotensi besar satu item pada satu waktu:

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);
}

Kode di atas menggunakan indeks sederhana sebagai pesan untuk RequestDataAsync panggilan. Kode sumber objek visualizer yang sesuai akan mengambil alih TransferData metode (bukan 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
        ...
    }
}

Sumber objek visualizer di atas memanfaatkan VisualizerObjectSource.DeserializeFromJson metode untuk mendeserialisasi pesan yang dikirim oleh penyedia visualizer dari incomingData.

Saat menerapkan penyedia visualizer debugger yang melakukan interaksi pesan kompleks dengan sumber objek visualizer, biasanya lebih baik meneruskan VisualizerTarget ke visualizer RemoteUserControl sehingga pertukaran pesan dapat terjadi secara asinkron saat kontrol dimuat. Meneruskan VisualizerTarget juga memungkinkan Anda mengirim pesan ke sumber objek visualizer untuk mengambil data berdasarkan interaksi pengguna dengan UI visualizer.

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
        ...
    }
    ...

Membuka visualizer sebagai Alat Windows

Secara default, semua ekstensi visualizer debugger dibuka sebagai jendela dialog modal di latar depan Visual Studio. Oleh karena itu, jika pengguna ingin terus berinteraksi dengan IDE, visualizer harus ditutup. Namun, jika Style properti diatur ke ToolWindow di DebuggerVisualizerProviderConfiguration properti , maka visualizer akan dibuka sebagai jendela alat non-modal yang dapat tetap terbuka selama sisa sesi debug. Jika tidak ada gaya yang dideklarasikan, nilai ModalDialog default akan digunakan.

    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);
    }

Setiap kali visualizer memilih untuk dibuka sebagai ToolWindow, itu harus berlangganan peristiwa StateChanged dari VisualizerTarget. Ketika visualizer dibuka sebagai jendela alat, visualizer tidak akan memblokir pengguna untuk membatalkan jeda sesi debug. Jadi, peristiwa tersebut akan ditembakkan oleh debugger setiap kali status target debug berubah. Penulis ekstensi visualizer harus memberikan perhatian khusus pada pemberitahuan ini karena target visualizer hanya tersedia ketika sesi debug aktif dan target debug dijeda. Ketika target visualizer tidak tersedia, panggilan ke ObjectSource metode akan gagal dengan 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");
            }
        }
    }
}

Pemberitahuan Available akan diterima setelah RemoteUserControl dibuat dan tepat sebelum dibuat terlihat di jendela alat visualizer yang baru dibuat. Selama visualizer tetap terbuka, nilai lain VisualizerTargetStateNotification dapat diterima setiap kali target debug mengubah statusnya. Pemberitahuan ValueUpdated digunakan untuk menunjukkan bahwa ekspresi terakhir yang dibuka oleh visualizer berhasil dievaluasi ulang di mana debugger berhenti dan harus di-refresh oleh UI. Di sisi lain, setiap kali target debug dilanjutkan atau ekspresi tidak dapat dievaluasi kembali setelah berhenti, Unavailable pemberitahuan akan diterima.

Memperbarui nilai objek yang divisualisasikan

Jika VisualizerTarget.IsTargetReplaceable benar, visualizer debugger dapat menggunakan ReplaceTargetObjectAsync metode untuk memperbarui nilai objek yang divisualisasikan dalam proses yang sedang di-debug.

Sumber objek visualizer harus mengambil CreateReplacementObject alih metode :

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

Cobalah RegexMatchDebugVisualizer sampel untuk melihat teknik ini beraksi.