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

时间:2012-01-12 16:45:29

标签: c# .net debugging

如果对象初始化为null,则为not possible to get the type information,因为引用不指向任何内容。

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

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

2 个答案:

答案 0 :(得分:10)

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

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

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

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

答案 1 :(得分:9)

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

  

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

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

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

  

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

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

  

为什么它比我更了解?

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

  

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

考虑取消引用null时实际发生的情况。例如,假设你这样做:

Fruit f = null;
string s = f.ToString();

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

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

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

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

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

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