... {
private string _ip = “127.0.0.1”;
public string IP ...{ get...{ return _ip; } set...{ _ip = value; } }
private int _port = 8080;
public int Port...{ get...{ return _port; } set...{ _port = value; } }
}
然后就用System.XML里面提供的API编写一个解析器,专门用于把class MyConnection的实例编码成下面的XML,并且能解析这个XML,把IP和Port的值重新读上来。
< IP > 127.0.0.1 </ IP >
< Port > 8080 </ Port >
</ MyConnection >
下面是MyConnection的解析器的代码片断,如果XML中节点比较多,或者有多级的子节点,下面的foreach和switch..case就会变得极其复杂,而且难于扩展。
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml (strXml);
XmlNodeList nodeList = xmlDoc.GetElementsByTagName ( " MyConnection " );
foreach (XmlNode node in nodeList)
... {
XmlNodeReader reader = new XmlNodeReader (node);
while (reader.Read())
...{
if(reader.NodeType==XmlNodeType.Element)
...{
string strEleName=reader.Name;
switch(strEleName)
...{
case "IP":
cfgInfo.IP = reader.ReadString();
break;
case "Port":
cfgInfo.Port = int.Parse(reader.ReadString());
break;
}
}
}
}
这里注明一下,其实从刚开始的时候,我们就没打算用System.Configuration来读写配置文件。因为它有个难以忍受的缺陷,就是在编码的时候必须把像IP,Port这样的key作为字符串硬敲在代码里面。我们的配置项目非常多,没有人记得这么多的key,所以还是决定把它们作为类的成员,至少这样可以用VS.Net提供的代码智能感知的功能把这些key罗列出来。
cfgInfo.Url = “http: // www.google.com”;
cfgInfo.IP = “ 127.0 . 0.1 ”;
cfgInfo.Port = 8080 ;
string strXml = cfgInfo.ToXmlString();
MyConnection cfgInfor = MyConfigFactory.Create( strXml, typeof (MyConnection) );
Console.Write( cfgInfor.IP + “ “ + cfgInfor.Port );
< Url > http://www.google.com </ Url >
< IP > 127.0.0.1 </ IP >
< Port > 8080 </ Port >
</ MyConnection >
咦,这不就是XML的序列化和反序列化么?我们才发现,原来读写配置信息的逻辑本质,就是XML和可编程对象之间的转换,只不过以前我们要为MyConnection编写专用的XML解析器,现在要实现的动态解析器,就是要把原来的静态机制设计成动态机制即可。
- 首先是的类成员访问机制:动态解析器需要在事先不知道类的定义的情况下,在运行时进行查询和访问其成员,这个问题可以用反射来解决。
- 然后就是类型动态转换机制:从各种CLR类型变成字符串很容易,Object.ToString()一下就可以,但从字符串变回这些类型,只有有限的几种TypeConverter,那我们先不追求完美,先实现几种常见类型的转换。
- 再者,从XML里读到一个字符串的值,你如何判断应该转换成那种类型呢?难道一定要在XML里面同时记录这种类型的信息么?比如<Port type=”int”>8080</Port>。其实未必,只要“Port”这个节点名称跟某个类里面的成员名称是一一对应的,就算XML节点打乱了顺序,也同样可以通过反射从metadata里面获得Port这个成员的类型。
... {
public abstract string ToXmlString();
internal abstract void LoadXmlNode( XmlNode node );
}
public class XObjectManager
... {
public static XObject CreateObject( string xmlString, Type objType )
...{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml( xmlString );
XmlNodeList nodelist = xmlDoc.GetElementsByTagName( objType.Name );
object item = objType.Assembly.CreateInstance( objType.ToString() );
objType.InvokeMember( " LoadXmlNode", BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.InvokeMethod,
null, item, new object[]...{nodelist[0]} );
return item;
}
}
... {
StringBuilder sb = new StringBuilder();
String tName = this.GetType().Name;
sb.Append( “<” + tName + “>” );
PropertyInfo[] list = this.GetType().GetProperties
( BindingFlags.Instance | BindingFlags.Public );
foreach( PropertyInfo prop in list )
...{
string pName = prop.Name;
sb.Append( “<” + pName + “>” );
object val = this.GetType().InvokeMember(pName,
BindingFlags.DeclaredOnly | BindingFlags.Public |
BindingFlags.Instance | BindingFlags.GetProperty,
null, this, new object[]...{} );
sb.Append( val.ToString() );
sb.Append( “</” + pName + “>” );
}
sb.Append( “</” + tName + “>” );
return sb.ToString();
}
internal void LoadXmlNode( XmlNode node )
... {
String tName = this.GetType().Name;
XmlNodeReader reader = new XmlNodeReader (node);
while (reader.Read())
...{
String nodeName = reader.Name;
if( tName == nodeName && reader.NodeType == XmlNodeType.EndElement ) break;
PropertyInfo [] pilist = this.GetType().GeProperty(nodeName);
Type type = pilist[0].PropertyType;
if( type == typeof( int ) )
...{
this.GetType().InvokeMember( name,
BindingFlags.DeclaredOnly | BindingFlags.Public |
BindingFlags.Instance | BindingFlags.SetProperty,
null, this, new object[]...{ int.Parse(newvalue) } );
}
}
}
程序像是一种有生命的东西,只要有拥有一颗好的种子,它就会不断地生根发芽,越长越大。完成这个最初的原型之后,我立即向下一个里程碑进军。即实现对嵌套对象的解析,以及XObjectCollection集合。其中的代码比较罗嗦,但基本都是沿用前面的思路,所以下面只是给出调用的例子。
... {
private string _url = “http://www.google.com”;
public string Url ...{ get...{ return _url; } set...{ _url = value; } }
private MyConnection _server = new MyConnection();
public int Server...{ get...{ return _server; } set...{ _server = value; } }
private MyConnectionList _clients = new MyConnectionList ();
public int Clients...{ get...{ return _ clients; } set...{ _ clients = value; } }
}
public class MyConnectionList : XObjectCollection
... {
public MyConnectionList() : base( typeof(MyConnection ) )
}
< Url > http://www.google.com </ Url >
< Server >
< IP > 127.0.0.1 </ IP >
< Port > 8080 </ Port >
</ Server >
< Client >
< MyConnection >
< IP > 127.0.0.1 </ IP >
< Port > 8080 </ Port >
</ MyConnection >
< MyConnection >
< IP > 127.0.0.1 </ IP >
< Port > 8080 </ Port >
</ MyConnection >
</ Client >
</ MyApplication >
实现这两个特性以后,我终于可以兴致勃勃地向同事们推介这套XML解析库了。一年以后,公司里几乎所有基于.NET开发的产品都使用了这种方式进行配置文件的读写,我再一次用代码证明了自己的存在。同时,我从开始也已经意识到,这种Object/XML Mapping的机制的应用远远不止于配置文件的读写,在Web Service以及一些医疗行业标准(比如HL7、CDA)的实现中也有很大用处,后来甚至还用来映射/解析XSL和XHTML这些格式更复杂的文本。因为它在定义托管类型的字段、属性、标签、以及类型之间聚合关系时候,就直接控制了XML节点的生成,从而把类的定义本身变成了一个XML解析器。两年以后,尽管这个XML解析库的代码还不到3000行,却已经增加了如命名空间支持、自由文本支持等高级的功能,并悄悄地渗透到公司的软件开发里面各个需要XML的角落。我记得有人评价COM技术的时候,说它最成功的地方其实是每个人都在使用它,但没有人感觉到它的存在。