[C#]有趣的VS扩展DebuggerVisualizer

公司的研发平台底层封装了一种类似于DataTable的数据结构,具有行列特性但是直接基于键值对,每次调试时想要查看其中的内容都非常困难。不由得想起第一次使用VS调试Dataset时候弹出的哪个框,鉴于此,搜索了大量资料,在走过一些弯路后终于尝试成功,特记录于此,以备后效。

1.几个主要的概念

  1.DebuggerVisualizer:MSDN的说明中描述了一些基础的步骤,这个类位于System.Diagnostics命名空间下,用来制定类型的可视化工具属性。另外,在Microsoft.VisualStudio.DebuggerVisualizer.dll程序集中,封装了诸如调试时弹出窗口的一些接口和默认实现。

     2.DialogDebuggerVisualizer,Microsoft.VisualStudio.DebuggerVisualizer.dll程序集中用来弹出调试时窗口的基类。

   3.VisualizerObjectSource,负责将当前要可视化的对象序列化或者反序列化,以便在组件间进行传输。这里的序列化说明后续的对象最好事先了ISerializable接口或者标记为Serializable。如果不是,默认的对象是报无法序列化错误,后面我们介绍如何处理。

    4.VisualizerDevelopmentHost可视化工具开发宿主,并调用宿主以显示可视化工具。后续会用来测试。

   5.微软提供了官方的DebuggerVisualizer模板类,可以下载,不过目前我还没找到。

    DebuggerVisualizer对于开发人员具有比较重要的意义,能够将一些不便于调试、查看和编辑的对象可视化,方便查看和编辑数据。

2.主要实现

   相关的代码非常简单,CodeProject上有人用10行代码实现了一个图片调试工具,我们就以这个代码举例

[assembly: System.Diagnostics.DebuggerVisualizer(typeof(ImageVisualizer.DebuggerSide),typeof(VisualizerObjectSource),
    Target = typeof(System.Drawing.Image),Description = "Image Visualizer")]
namespace ImageVisualizer
{
    public class DebuggerSide : DialogDebuggerVisualizer
    {
        override protected void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            Image image = (Image)objectProvider.GetObject();
            
            Form form = new Form();
            form.Text = string.Format("Width: {0}, Height: {1}", image.Width, image.Height);
            form.ClientSize = new Size(image.Width, image.Height);
            form.FormBorderStyle = FormBorderStyle.FixedToolWindow;
            
            PictureBox pictureBox = new PictureBox();
            pictureBox.Image = image;
            pictureBox.Parent = form;
            pictureBox.Dock = DockStyle.Fill;

            windowService.ShowDialog(form);

            // 如果在弹出窗口修改对象的数据,可以将objectProvider对象传给Form
            // 然后将修改后的数据传输回去
            // 需要注意,必须判断当前调试状态是否允许替换原本的调试初始值
            //TextBox text = win.FindName("txtValue") as TextBox;
            //Int32 newValue = Convert.ToInt32(text.Text);
            //if (objProvider.IsObjectReplaceable)
            //{
            //    objProvider.ReplaceObject(newValue);
            //}
        }
    }

  命名空间上的声明可以让VS识别该程序集,后续DebuggerSide 类重写了Show方法,通过IDialogVisualizerService的实例创建一个弹出窗口,objectProvider则将对象从编辑器中传输到处理函数以便进行后续处理。上文处理的对象是Image,如果该对象是不可序列化的对象,那么我们需要利用System.Diagnostics.DebuggerVisualizer的第二个参数。

  调试器端和调试对象端使用 VisualizerObjectSource 和 IVisualizerObjectProvider 相互通信。所以,对于未标记为序列化的对象,我们需要重写该类的部分方法,将对象以可序列化的方式进行传输。简而言之,对对象进行封装处理。

    public class ImageSource : VisualizerObjectSource
    {
        /// <summary>
        /// 用来将输入的数据进行处理和封装,转换为流以便在组件间通讯
        /// </summary>
        /// <param name="target">当前查看的对象</param>
        /// <param name="outgoingData">输出流</param>
        public override void GetData(object target, System.IO.Stream outgoingData)
        {
            // 获取调试对象
            Image image = target as Image;
            // 将不可序列化的对象转换为可序列化对象
            // Image本身是可序列化的,此处举例说明
            SerializableImage newImage = image as SerializableImage;
            //  传输对象
            base.GetData(newImage, outgoingData);
        }
    }

    public class SerializableImage : Image, ISerializable
    {
        // TODO: 序列化对象
    }

   这个WPF的教程更加仔细的介绍了一些步骤,包括如果更改调试变量的数据等。

  测试代码如下,任意创建控制台或者Winform等程序,启动即可调试:

        public static void TestVisualizer(object source)
        {
            // 直接使用当前自定义的调试器和数据处理对象类型,即可初始化进行测试
            VisualizerDevelopmentHost visualizeHost = new VisualizerDevelopmentHost(source,
                 typeof(DebuggerSide), typeof(ImageSource));
            visualizeHost.ShowVisualizer();
        }

    3.部署

  部署时也比较简单,直接将Dll复制到对应VS的安装目录下的\Common7\Packages\Debugger\Visualizers目录即可。你可以将下述命令修改保存为批处理,在程序集目录执行即可。

copy DictionaryVisualizer2010.dll "C:\Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers" /y

copy ListVisualizer2010.dll "C:\Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers" /y

copy VisualizerLib.dll "C:\Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers" /y

    4.说明

  作为一种比较重型的扩展,有些情况我们可能需要对一些List或者自定义对象,在调试时无需弹出窗口,而希望直接鼠标置于其上时显示如数量或者关键字等信息,那么可以直接使用DebuggerDisplay和DebuggerTypeProxy 进行处理。CodeProject也有文章介绍了这方面的东西。下面是一些复制来的代码:  

    using System;
    using System.Collections;
    using System.Data;
    using System.Diagnostics;
    using System.Reflection;

    class DebugViewTest
    {
        // The following constant will appear in the debug window for DebugViewTest. 
        const string TabString = "    ";
        // The following DebuggerBrowsableAttribute prevents the property following it 
        // from appearing in the debug window for the class.
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static string y = "Test String";

        static void Main()
        {
            Hashtable hash = new Hashtable();
            MyHashtable myHashTable = new MyHashtable();
            DataSet ds = new DataSet();
            DataTable dt = new DataTable();
            ds.Tables.Add(dt);
            myHashTable.Add("one", 1);
            myHashTable.Add("two", 2);

            DBTable table = new DBTable();
            //table.Columns.Add("TEST1");
            //table.Columns.Add("TEST2");
            //table.Columns.Add("TEST3");
            //table.Rows.Add("1", "2", "3");
            //table.Rows.Add("1", "2", "3");
            //table.AcceptChanges();

            Console.WriteLine(myHashTable.ToString());
            Console.WriteLine("In Main.");
        }
    }
    [DebuggerDisplay("{value}", Name = "{key}")]
    internal class KeyValuePairs
    {
        private IDictionary dictionary;
        private object key;
        private object value;

        public KeyValuePairs(IDictionary dictionary, object key, object value)
        {
            this.value = value;
            this.key = key;
            this.dictionary = dictionary;
        }
    }
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(HashtableDebugView))]
    class MyHashtable : Hashtable
    {
        private const string TestString = "This should not appear in the debug window.";

        internal class HashtableDebugView
        {
            private Hashtable hashtable;
            public const string TestString = "This should appear in the debug window.";
            public HashtableDebugView(Hashtable hashtable)
            {
                this.hashtable = hashtable;
            }

            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public KeyValuePairs[] Keys
            {
                get
                {
                    KeyValuePairs[] keys = new KeyValuePairs[hashtable.Count];

                    int i = 0;
                    foreach (object key in hashtable.Keys)
                    {
                        keys[i] = new KeyValuePairs(hashtable, key, hashtable[key]);
                        i++;
                    }
                    return keys;
                }
            }
        }
    }

   

原文地址:https://www.cnblogs.com/tukzer/p/3069128.html