XmlSerializer我想现在用的人可能不多了,大家都在用Json。我现在所在的公司依然在用,所以发现了这个坑。当然这个坑存在很久了只是没用过所以才发现。
事情是这样的,测试那边说系统偶尔会报找不到 xxxx.XmlSerizlizers 的引用,File Not Found的异常,几率不高。但是我百般寻找发现项目了根本就没有这个dll,为什么会找这个dll呢?
后来经过各种查找原因,发现是项目引用了公司的一个框架,这个框架记录了此异常,他是如何记录的呢?
internal static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e) { if (HttpContext.Current == null || e.Exception == null || e.Exception.Source == "Arch.CFramework.StartUp") return; if (HttpContext.Current.Items[(object) "__RequestFirstChangeExceptionKey__"] == null) HttpContext.Current.Items[(object) "__RequestFirstChangeExceptionKey__"] = (object) new RequestException(); (HttpContext.Current.Items[(object) "__RequestFirstChangeExceptionKey__"] as RequestException).Exceptions.Add(e.Exception); }
它记录了FirstChangeException,只要有FirstChangeException,他就会记录下来,看来XmlSerializer会有这个异常。于是在new XmlSerializer的时候F10,跟踪进去发现确实触发了FileNotFound的异常,找Assembly.Load找不到xxxx.XmlSerializers引用。这台奇怪了,就new下XmlSerializer为什么会找不相干的DLL?
搜索google找到了这篇问答:http://stackoverflow.com/questions/1127431/xmlserializer-giving-filenotfoundexception-at-constructor
这里面说的比较清楚,
Like Martin Sherburn said, this is normal behavior. The constructor of the XmlSerializer first tries to find an assembly named [YourAssembly].XmlSerializers.dll which should contain the generated class for serialization of your type. Since such a DLL has not been generated yet (they are not by default), a FileNotFoundException is thrown. When that happenes, XmlSerializer's constructor catches that exception, and the DLL is generated automatically at runtime by the XmlSerializer's constructor (this is done by generating C# source files in the %temp% directory of your computer, then compiling them using the C# compiler). Additional constructions of an XmlSerializer for the same type will just use the already generated DLL.
.net的实现机制是先去找[YourAssembly].XmlSerializers.dll,找不到就会抛出FileNotFoundExcpetion,然后XmlSerializer的构造函数捕获到这个异常之后,就会动态生成这个dll放在%temp%下,然后再用它。我勒个擦,这不是很坑爹的机制吗?到了4.5版本,就不在这么实现了。
Starting from .NET 4.5, XmlSerializer no longer performs code generation nor does it perform compilation with the C# compiler in order to create a serializer assembly at runtime, unless explicitly forced to by setting a configuration file setting (useLegacySerializerGeneration). This change removes the dependency on csc.exe and improves startup performance. Source: .NET Framework 4.5 Readme, section 1.3.8.1.
现在的问题是,这个异常一般是不会有人知道的,除非捕获FirstChangeException。
虽然这个异常只会发生一次,但是如果应用程序池回收了(XmlSerializer会缓存Assembly),%temp%没有了,就会重新生成,还是会有一点点的影响,总是让人不舒服。
所幸,帖子中有提到一个方法XmlSerializer.FromTypes,这个不会触发异常,但他不会利用缓存,说是会内存泄露,如下:
WARNING: You will leak memory like crazy if you use this method to create instances of XmlSerializer for the same type more than once! This is because this method bypasses the built-in caching provided the XmlSerializer(type) and XmlSerializer(type, defaultNameSpace) constructors (all other constructors also bypass the cache). If you use any method to create an XmlSerializer that is not via these two constructors, you must implement your own caching or you'll hemorrhage memory. – Allon Guralnek
这个实验,我就不做了,不会缓存没问题,我们自己缓存便是。所以我写了一个测试程序,
class Program { private static Dictionary<Type, XmlSerializer> _cache = new Dictionary<Type, XmlSerializer>(); private static XmlSerializer GetSerializer<T>() { var type = typeof(T); if (_cache.ContainsKey(type)) { return _cache[type]; } var serializer = XmlSerializer.FromTypes(new[] { typeof(Test) }).FirstOrDefault(); _cache.Add(type, serializer); return serializer; } private static void Serializer<T>(XmlSerializer xmlSerializer,T ob) { MemoryStream memoryStream = new MemoryStream(); xmlSerializer.Serialize((Stream)memoryStream, ob); var str = Encoding.UTF8.GetString(memoryStream.GetBuffer()); memoryStream.Close(); memoryStream.Dispose(); } static void Main(string[] args) { var ns = typeof(Test).Namespace; var maxtimes = 1000*1000; var t = new Test { Name = "test" }; var sw = new Stopwatch(); sw.Start(); for (var i = 0; i < maxtimes; i++) { var s = GetSerializer<Test>(); //Serializer(s, t); } sw.Stop(); Console.WriteLine("FromType:" + sw.ElapsedMilliseconds + " ms"); sw = new Stopwatch(); sw.Start(); for (var i = 0; i < maxtimes; i++) { var s = new XmlSerializer(typeof(Test)); //Serializer(s, t); } sw.Stop(); Console.WriteLine("New:" + sw.ElapsedMilliseconds + " ms"); } }
缓存的只用100ms,直接new的用了6000ms。显然用FromTypes最好,不但解决了异常问题,还提升了效率。
于是顺手写了一个XmlSerializerHelper类,供大家参考使用:
public static class XmlSerializerHelper { private static ConcurrentDictionary<Type, XmlSerializer> _cache; private static XmlSerializerNamespaces _defaultNamespace; static XmlSerializerHelper() { _defaultNamespace = new XmlSerializerNamespaces(); _defaultNamespace.Add(string.Empty, string.Empty); _cache = new ConcurrentDictionary<Type, XmlSerializer>(); } private static XmlSerializer GetSerializer<T>() { var type = typeof(T); return _cache.GetOrAdd(type, XmlSerializer.FromTypes(new[] { type }).FirstOrDefault()); } public static string XmlSerialize<T>(this T obj) { using (var memoryStream = new MemoryStream()) { GetSerializer<T>().Serialize(memoryStream, obj, _defaultNamespace); return Encoding.UTF8.GetString(memoryStream.GetBuffer()); } } public static T XmlDeserialize<T>(this string xml) { using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml))) { var obj = GetSerializer<T>().Deserialize(memoryStream); return obj == null ? default(T) : (T)obj; } } }
【PS】假如你的项目有用到new XmlSerializer,那么想看到异常很简单。
1、Tools—>Options—>Debugging—>Enable Just My Code勾去掉
2、Debug—>Exceptions—>Common Language Runtime Exceptions 找到System.IO.FileNotFoundException,throw勾打上 ,再F5调试看看。