调试器如何获取有关初始化为null的对象的类型信息?

Abe*_*bel 9 .net c# debugging

如果将对象初始化为null,则无法获取类型信息,因为引用不指向任何内容.

但是,当我调试并将鼠标悬停在变量上时,它会显示类型信息.只显示静态方法,但似乎仍然知道类型.甚至在发布版本中.

调试器是否使用其他信息而不仅仅是某种反射来查找数据类型?怎么知道比我更了解?如果它知道这一点,为什么它不能显示数据类型NullReferenceException

Jar*_*Par 10

您似乎将引用的类型与其指向的值的类型混淆.引用的类型嵌入到DLL元数据中,并且调试器可以轻松访问.还存在相关PDB中存储的附加信息,调试器利用该信息来提供更好的体验.因此,即使对于空引用,调试器也可以确定类型和名称之类的信息.

至于NullReferenceException.它能告诉你它查询字段/方法的类型......可能.我不熟悉CLR这一部分的内部结构,但似乎没有一个固有的原因,为什么它不能这样做.

但我不确定CLR的额外成本是否值得.我对缺乏引用异常的信息缺乏感到沮丧.但更多涉及的类型我想要名字!我不在乎它是一个IComparable,我想知道它是leftCustomer.

名称有点像CLR并不总是有权访问,因为它们中的很大一部分存在于PDB而不是元数据中.因此它无法为它们提供极高的可靠性(或速度)


Eri*_*ert 9

贾里德的答案当然是正确的.只是添加一点:

当我调试并将鼠标悬停在变量上时,它会显示类型信息

对.你有一个碗.碗被标记为"水果".碗是空的.碗里的水果是什么类型的?你不能说,因为碗里没有任何水果.但这并不意味着你对碗一无所知.你知道碗里可以装任何水果.

当您将鼠标悬停在变量上时,调试器可以告诉您变量本身或其内容.

调试器是否使用其他信息而不仅仅是某种反射来查找数据类型?

绝对.调试器不仅需要知道此引用所引用的事物的类型,还需要知道对此变量中可存储的内容的限制.有关对特定存储位置施加什么限制的所有信息都是运行时已知的,运行时可以将该信息告知调试器.

怎么知道比我更了解?

我拒绝这个问题的前提.调试器代表您运行; 它不能做你不能做的事.如果您不知道特定变量的类型限制是什么,那不是因为您缺乏查找的能力.你还没有看过.

如果它知道这一点,为什么它不能在NullReferenceException中显示数据类型?

想一想当你取消引用null时实际发生的事情.例如,假设你这样做:

Fruit f = null;
string s = f.ToString();
Run Code Online (Sandbox Code Playgroud)

Fruit中的ToString可能会重载.抖动必须生成什么代码?假设局部变量f存储在堆栈位置.抖动说:

  • 将与f相关联的堆栈指针偏移处的存储器地址的内容复制到寄存器1
  • 虚函数表将是,从该指针的顶部开始说是8个字节,而ToString将是该表顶部的四个字节.(我只是把这些数字提升了;我不知道真正的偏移是什么偏离我的头脑.)所以,首先在寄存器1的当前内容中加8.
  • 现在取消引用寄存器1的当前内容,将vtable的地址输入寄存器2
  • 现在向寄存器2添加四个字节
  • 现在我们有一个指向ToString方法的指针......

但是等一下,让我们再次遵循这个逻辑.第一步将零置于寄存器1中,因为f包含null.第二步增加了八个.第三步取消引用指针0x00000008,虚拟内存系统发出异常,指出刚刚触摸了非法内存页面.CLR处理异常,确定异常发生在第一个64 K的内存上,并猜测有人刚刚取消引用了空指针.因此,它创建一个空引用异常并抛出它.

虚拟内存系统肯定不知道它取消引用指针0x00000008的原因是因为有人试图调用f.ToString().这些信息在过去就丢失了; 记忆经理的工作就是告诉你什么时候你碰到了你没有任何权利的东西; 为什么你试图触摸你不拥有的内存不是它的工作要弄明白.

CLR可以维护一个单独的辅助数据结构,这样每次访问内存时,它都会记录您尝试这样做的原因.这样,异常可以包含更多信息,描述异常发生时您正在做什么.想象一下为每次访问内存维护这样一个数据结构的成本!托管代码可能比现在慢十倍,并且成本与正确的代码和破坏的代码一样严重.为了什么?告诉你你可以轻易搞清楚自己:你解除引用的包含null的变量.

该功能不值得花费,因此CLR不会这样做.技术上没有理由说不能; 这是不切实际的.