谈谈XamlXmlReader.HasLineInfo属性的运行原理
一、写作背景
最近,笔者在使用.NET 4.0 中的XamlXmlReader来自定义Xaml文件的反序列化过程时,发现XamlXmlReader.HasLineInfo属性存在一个很奇怪的特性,即同样的上下文在调试状态时该属性的值为true,而在非调试状态时该属性值为false。当时笔者就冒了一身冷汗,要知道程序员都是习惯在调试状态下来运行程序的,如此,即便是该问题从用户或测试部门那反馈回来,问题的根本原因也将很难定位!冷静下来后,笔者分析该问题应该是由.NET内部代码导致。然而网上并查不到相关的描述,于是乎笔者决定自己来研究这个问题,并最后将研究的成果以博文的形式发表出来,一来是方便自己日后备查,另一方面也为同样遇到该问题的朋友增加一条真正需要的搜索结果。
二、问题详述
为了方便说明问题,笔者写了个例程,就两行:首先创建一个XamlXmlReader实例,然后显示出该实例的HasLineInfo的值。代码如下:
然后笔者在调试环境和非调试环境中分别运行该例程,得到了完全相反的两种结果:
图 1调试环境的运行结果
图 2非调试环境的运行结果
三、原理分析
经过分析,笔者认为同样的例程在不同环境下出现完全相反的结果,原因的落脚点应该是在属性HasLineInfo值的确定时间上,而在上述代码中能够确定该属性值的无非是以下两处:
- XamlXmlReader的构造函数;
- 属性HasLineInfo的定义。
接着,笔者通过使用Reflactor查看了XamlXmlReader. HasLineInfo的实现代码,代码如下:
public bool HasLineInfo
{
get
{
return this._mergedSettings.ProvideLineInfo;
}
}
其中,成员_mergedSettings的定义如下:
private XamlXmlReaderSettings _mergedSettings;
接着再继续查看XamlXmlReaderSettings.ProvideLineInfo的定义:
public bool ProvideLineInfo
{
[CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this.<ProvideLineInfo>k__BackingField;
}
[CompilerGenerated, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
set
{
this.<ProvideLineInfo>k__BackingField = value;
}
}
很显然,属性HasLineInfo的定义中不存在对运行调试环境进行适配的代码。因此,最终目标将直接定位到原因1,即XamlXmlReader的构造函数中。下面再来看XamlXmlReader构造函数的实现代码。其中,需要注意的是XamlXmlReader给出了16个构造函数,但这些构造函数全都指向了一个私有函数,所以在本文中只需对例程1中用到的构造函数进行分析即可。构造函数代码如下:
public XamlXmlReader(string fileName)
{
if (fileName == null)
{
throw new ArgumentNullException("fileName");
}
this.Initialize(this.CreateXmlReader(fileName, null), null, null);
}
在该构造函数中调用了私有函数Initialize,该函数的定义代码如下:
private void Initialize(XmlReader givenXmlReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings)
{
XmlReader reader;
this._mergedSettings = (settings == null) ? new XamlXmlReaderSettings() : new XamlXmlReaderSettings(settings);
if (!this._mergedSettings.SkipXmlCompatibilityProcessing)
{
reader = new XmlCompatibilityReader(givenXmlReader, new IsXmlNamespaceSupportedCallback(this.IsXmlNamespaceSupported)) {
Normalization = true
};
}
/*以下省略*/
}
在上面的函数定义中,大家可以注意一下这段代码:
this._mergedSettings = (settings == null) ? new XamlXmlReaderSettings()
: new XamlXmlReaderSettings(settings);
该代码的作用是初始化私有变量_mergedSettings,因此为了检验在该行代码后是否会存在关于_mergedSettings.ProvideLineInfo的环境适配代码,笔者修改了上一例程,创建了一个XamlXmlReaderSettings实例,同时将该实例的ProvideLineInfo属性值设为true,即强制其提供LineInfo,最后将该实例传给XamlXmlReader的构造函数,代码如下:
XamlXmlReaderSettings settings = new XamlXmlReaderSettings();
settings.ProvideLineInfo = true;
XamlXmlReader reader = new XamlXmlReader(@"E:\BasicFillSymbol.xaml", settings);
MessageBox.Show(reader.HasLineInfo ? "HasLineInfo" : "don't HasLineInfo");
然后,同样在调试环境和非调试环境下运行该例程,笔者发现,两次结果完全一样:
图 3例程二运行结果
因此,可以判定_mergedSettings.ProvideLineInfo的环境适配代码一定是出现在该行代码上,且定为代码new XamlXmlReaderSettings()所致。于是,笔者又进一步剖析XamlXmlReaderSettings的构造函数,结果发现了这个:
[TargetedPatchingOptOut("Performance critical to inline this type of
method across NGen image boundaries")]
public XamlXmlReaderSettings()
{
}
但笔者不甘心,于是又查看了XamlXmlReaderSettings的基类,原来XamlXmlReaderSettings是继承于类XamlReaderSettings的。接着查看XamlReaderSettings的默认构造函数,代码如下:
public XamlReaderSettings()
{
this.InitializeProvideLineInfo();
}
private void InitializeProvideLineInfo()
{
if (Debugger.IsAttached)
{
this.ProvideLineInfo = true;
}
else
{
this.ProvideLineInfo = false;
}
}
皇天不负有心人,终于被笔者找到Debugger.IsAttached这行代码了!该代码的作用正是帮助程序在运行时动态查看自己是否处于调试状态。
四、结论
根据以上的分析,大家可以发现在构造XamlXmlReader实例时,如果未传入自定义的XamlXmlReaderSettings实例,则构造函数会给自动创建一个。同时,XamlXmlReaderSettings基类的默认构造函数会根据进程当前所处的调试状态自动为属性ProvideLineInfo赋上对应的初值,从而也就导致了本文开始谈到的那个问题。
写到这里,笔者不经有些感慨:其实小细节中往往蕴含着大玄机,同样一段代码在调试和非调试状态下运行是有区别的,不清楚这点,会吃大亏!所以,程序员对代码的态度也应该和科学家对学问的态度一样,一定不要放过任何疑点,不然就是在害人害己。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权,作者将保留追究法律责任的权利。另外,也请转载者在评论处留下转载信息,谢谢合作。