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 :
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 divisualisasikanVisualizerTarget
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 DataSet
objek , , DataTable
DataView
, 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
ataunet6.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 alihGetData
penulisantarget
nilai serial keoutgoingData
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;
}
Konten terkait
Cobalah RegexMatchDebugVisualizer
sampel untuk melihat teknik ini beraksi.