2006年02月18日

头一次来杭州,连续忙了10天。没想到在飘雪的日子去了西湖。本来一直打算找个时间去的,如今下了雪,毕竟这种景致是稀少的,所以就决定去了。独自一人就上路了。从同事那里知道,办公室离西湖走路只需10多分钟,知道大致的方向。拿了把伞,还是早上出门时从宾馆借的,迎着飞落的雪花,踏着已经积聚了雪水的路面,朝着哪个方向出发了。延安路上的人总是那么多。穿过延安路,沿着浙大校园北边的一条小路,继续走着,脑子里仍然是工作的烦心事。

(2.26报时间过得真快, 那场雪仍然清晰可见)

沿着小路, 10多分钟后,我来到了西湖. 原来只听说这里是开放式的旅游区,但当我真正来到这里时, 还是比较没有心理准备,就这么就来了,不自觉的就融入了这个人间仙镜。横过一条马路,就进入了风景区。此时,已能感觉到人来人往,越往里走,人越发多了起来。早晨的雪让空气变得尤其干净和潮湿,感觉来这里的每个人的身上都带着露水。雪依然在断断续续的下。穿过一些灌木丛,终于看到了久违的湖面,在一层薄雾笼罩下,朦胧得很,但仍然能感觉到湖面的开阔。

2006年02月07日

在.Net中, 经常我们会利用Excel来展现数据. 我们知道此时, Excel做为一个进程外COM被激活. 可是却无法彻底关闭该进程. 这是什么原因呢?

COM的生命周期依靠计数机制,只有当引用计数为零时,该COM才会被释放.

.Net程序能调用COM,依赖于RCW(运行库可调用包装)组件,它负责把.Net方法的调用参数转化为COM接口中的参数,并调用相应的COM接口, 然后将返回结果最终返回给.net程序的方法.

因此,当在.Net中操作Excel的方法时,意味着RCW在引用Excel中的对象,所以为了彻底关闭Excel,必须将对于这些对象的引用计数清零.在.Net中提供了Marshal.ReleaseComObject .

为了方便起见,可封装如下方法:

  private void NAR(object o)
  {
   try
   {
    System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
   }
   catch {}
   finally
   {
    o = null;
   }
  }

由于垃圾回收的原因,CLR并不会立即回收对象.所以为了能够立即回收,那么可以调用GC.Collect()来迫使垃圾回收器起作用.

具体的例子请参考: http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B317109

2006年01月27日

.net中的泛型和c++泛型有很多的区别,为了加强类型安全,.net增加了很多语言层面的约束。

下面的文章有深入的讲解:http://msdn.microsoft.com/msdnmag/issues/03/10/NET/

3.0中最引人注目的就是LINQ了(http://msdn.microsoft.com/netframework/future/linq/)。它将强大的查询功能集成到了语言内部,这种思想要大大超前于传统的ORM。它的引入也给传统的程序语言带来了巨大的冲击。我会继续关注和学习。     

MSDN给出了详细的指导:

Design Guidelines for Class Library Developers

其中,包括了命名规则(Naming Guidelines)

.net中凡是实现了IDictionary接口的类型,XML序列化器均无法接受。既然WebServices使用了XML序列化,那么其方法就无法直接使用Hashtable。

于是,可以采用间接的方法,即:将Hashtable转换成二维交错数组,具体方法如下:

  private object[][] ToJaggedArray
  {
   get
   {
    object[][] oo = new object[data.Count][];
    int i = 0;
    foreach (object key in data.Keys)
    {
     oo[i] = new object[]{key, data[key]};
     i++;
    }
    return oo;
   }
  }

  private Hashtable ToHashtable
  {
   get
   {
    Hashtable ht = new Hashtable(data.Length);
    foreach(object[] pair in data)
    {
     object key = pair[0];
     object value = pair[1];
     ht[key] = value;
    }
    return ht;
   }
  }

我们知道,DataSet可以在Remoting或者WebServices中传递,因为它实现了ISerializable接口。但是,DataSet的序列化并非真正的binary,而是先转换成XML,然后再binary,所以它的尺寸可想而之。

对于大记录数的DataSet,如果不优化其尺寸,那么必然会影响传输效率。如何优化呢?

目前,关于这方面的文章有如下几篇:

1.http://msdn.microsoft.com/msdnmag/issues/02/12/cuttingedge/default.aspx?side=true

2.http://support.microsoft.com/kb/829740

3.http://www.eggheadcafe.com/articles/20031219.asp

4.http://www.microsoft.com/taiwan/msdn/columns/adonet/AdoNet_20041231.htm

其中,第三篇中说到其效率很高。但实际应用时,发现在不同操作系统/硬件环境的机器之间,解压缩时会发生错误。

其实,最直接的优化尺寸的办法就是压缩。下面给出了采用#ziplib,如何来封装DataSet的序列化和反序列化:

 public class CompressDataSet
 {
  public static DataSet ReadCompressStream(Stream data)
  {
   if(data != null)
    data.Seek(0, SeekOrigin.Begin);

   int s1 = (int)data.Length;
   Console.WriteLine("compressed size: " + s1);
   
   MemoryStream m = new MemoryStream();
   InflaterInputStream mem = new InflaterInputStream(data);
   byte[] buffer = new byte[4096];
   while(true)
   {
    int size = mem.Read(buffer, 0, buffer.Length);
    m.Write(buffer, 0, size);
    if(size == 0)
     break;
   }
   mem.Close();

   Console.WriteLine("original size: " + m.Length);
   Console.WriteLine("compress ratio: {0}", (s1 * 1.0 / m.Length).ToString("0.###"));

   m.Seek(0, SeekOrigin.Begin);
   BinaryFormatter bf = new BinaryFormatter();
   DataSet ds = bf.Deserialize(m) as DataSet;
   m.Close();
   return ds;
  }

  public static Stream WriteCompressDataSet(DataSet data)
  {
   MemoryStream mem = new MemoryStream();
   BinaryFormatter bf = new BinaryFormatter();
   bf.Serialize(mem, data);   
   byte[] buffer = mem.ToArray();
   mem.Close();

   MemoryStream memC = new MemoryStream();
   ICSharpCode.SharpZipLib.Zip.Compression.Deflater  defl =
    new  ICSharpCode.SharpZipLib.Zip.Compression.Deflater(9);
   DeflaterOutputStream mem1 = new DeflaterOutputStream(memC, defl);
   mem1.Write(buffer, 0, buffer.Length);
   mem1.Close();

   MemoryStream m = new MemoryStream(memC.ToArray());
   return m;
  }
 }

XML序列化错误:找不到文件或程序集名称“effvdqz0.dll”,或找不到它的一个依赖项

详细的堆栈如下:

StackTrace "   at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Boolean isStringized, Evidence assemblySecurity, Boolean throwOnFileNotFound, Assembly locationHint, StackCrawlMark& stackMark)\r\n   at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Boolean stringized, Evidence assemblySecurity, StackCrawlMark& stackMark)\r\n   at System.Reflection.Assembly.Load(AssemblyName assemblyRef, Evidence assemblySecurity)\r\n   at System.CodeDom.Compiler.CompilerResults.get_CompiledAssembly()\r\n   at System.CodeDom.Compiler.CompilerResults.get_CompiledAssembly()\r\n   at System.Xml.Serialization.Compiler.Compile()\r\n   at System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings)\r\n   at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlTypeMapping xmlTypeMapping)\r\n   at System.Xml.Serialization.XmlSerializer..ctor(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, String defaultNamespace)\r\n   at System.Xml.Serialization.XmlSerializer..ctor(Type type, XmlAttributeOverrides overrides)\r\n

上面那个dll即是.net动态生成的。为什么会找不到呢?

通常的原因是,当前帐户的临时目录下确实不能生成该dll,可能是权限的原因。

然而,还存在另一种原因。即:由于动态编译失败,所以dll没有生成。如下场景下会发生这种事情:

我们知道,XML序列化支持类型重载。

当动态添加一个数组属性的XmlElement标签后,竟然发现GenerateTempAssembly方法生成的cs文件中,会将该数组强行转换为XmlElement标签中的类型(是一个强类型数组),由于这中转化必然是编译不过的,所以就不会生成dll文件,自然就会出现上面的问题。

解决办法:

添加一个XmlElment标签后,问题解决了。:)

既然每次序列化时,.net都会动态生成程序集,那么这个过程必然会影响程序效率。而实际上,当类型确定后,那么该程序集的内容也就确定了。所以,能否有一个工具来产生该程序集,然后,程序直接引用它,岂不解决了效率问题?

目前,在.net 2.0中,已经提供了这样的工具(XML Serializer Generator Tool)sgen.exe。但.net 1.1中没有该工具。下面给出了一种半自动的方法:

使用SpeedSerializer的步骤:
1.将Xml序列化开关打开,即:将下面的内容放入执行程序的配置文件中。
     <system.diagnostics>
         <switches>
              <add name="XmlSerialization.Compilation" value="4" />
         </switches>
     </system.diagnostics>
2.运行原始的XmlSerializer的构造函数(将序列化对象的类型传入),便可从当前帐户的临时目录中,获取到需要序列化类型的IO文件(*.cs)
3.将该文件和SpeedSerializer.cs引入到工程中
4.如果要序列化的对象的类型还未引入工程,那么将其引入
5.将该文件中的Writer类的基类替换成XmlSpeedSerializationWriter;
将该文件中的Reader类的基类替换成XmlSpeedSerializationReader
6.序列化:实例化Writer类,调用其Write方法
反序列化:实例化Reader类,调用其Read方法

SpeedSerializer.cs的源代码如下:

using System;
using System.Xml;
using System.IO;
using System.Reflection;

namespace Core.Service.XmlSerializer
{
 public abstract class XmlSpeedSerializationWriter : System.Xml.Serialization.XmlSerializationWriter
 {
  public XmlSpeedSerializationWriter(){}

  public XmlSpeedSerializationWriter(Stream s)
  {
   this.Init(s);
  }

  public  XmlSpeedSerializationWriter(string fileName)
  {
   this.Open(fileName);   
  }

  private void Init(Stream s)
  {
   this.Writer = new XmlTextWriter(s, System.Text.Encoding.GetEncoding(936)); 
  
   XmlTextWriter xw = this.Writer as XmlTextWriter;
   xw.Formatting = Formatting.Indented;
  }

  public void Close()
  {
   if(this.Writer != null)
    this.Writer.Close();
  }

  /// <summary>
  /// 序列化对象
  /// </summary>
  /// <param name="fileName">要保存的目标文件的名称</param>
  /// <param name="o">序列化的对象</param>
  /// <param name="method">IO文件中的根元素对应的方法名称</param>
  public void Write(string fileName, object o, string method)
  {
   FileStream fs = new FileStream(fileName, FileMode.Create);
   this.Init(fs);      
   
   this.GetType().InvokeMember(method, BindingFlags.InvokeMethod, null, this, new object[]{o});
   this.Close();
  }

  private void Open(string fileName)
  {
   FileStream fs = new FileStream(fileName, FileMode.Create);
   this.Init(fs);  
  }
 } 

 public abstract class XmlSpeedSerializationReader : System.Xml.Serialization.XmlSerializationReader
 {
  XmlReader reader;

  public XmlSpeedSerializationReader(){}

  public XmlSpeedSerializationReader(Stream s)
  {
   this.Init(s);
  }

  public XmlSpeedSerializationReader(string fileName)
  {
   Open(fileName);
  }

  private void Init(Stream s)
  {
   reader = new XmlTextReader(s);
   InitIDs();
  }

  /// <summary>
  /// 反序列化
  /// </summary>
  /// <param name="fileName">目标文件名称</param>
  /// <param name="method">IO文件中的读取根元素的方法名称</param>
  /// <returns>反序列化的对象</returns>
  public object Read(string fileName, string method)
  {
   this.Open(fileName);
   
   object o = this.GetType().InvokeMember(method, BindingFlags.InvokeMethod, null, this, null);
   this.Close();
   
   return o;
  }

  private void Open(string fileName)
  {
   FileStream fs = new FileStream(fileName, FileMode.Open);
   this.Init(fs);      
  }

  private void Close()
  {
   if(this.Reader != null)
    this.Reader.Close();
  }

  protected new bool ReadNull (){ return false; }
  protected new XmlQualifiedName GetXsiType(){ return null; }
  protected new void ReadEndElement(){}

  protected new XmlReader Reader
  {
   get{ return this.reader; }
  }
 }


}

如果开关XML元素放置的位置不正确,即:没有放在根元素下,那又会发生什么事情呢?

比如:放在<runtime>元素下,

  <runtime>
     <system.diagnostics>
         <switches>
              <add name="XmlSerialization.Compilation" value="4" />
         </switches>
     </system.diagnostics>
  </runtime>

会产生如下结果:

1. 仍然看不到动态生成的程序集

2. 序列化时,会出现“***.dll中未找到**类型”的错误

注意:序列化会在很多场景下发生,包括:

显式的调用序列化器方法

Remoting方法调用时

WebServices方法调用时