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 = *;
var t = new Test { Name = "test" };
var sw = new Stopwatch();
sw.Start();
for (var i = ; 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 = ; 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调试看看。

【经验谈】XmlSerializer的坑的更多相关文章

  1. C#中XmlSerializer的内存占用问题

    被XmlSerializer掉坑里了,爬了一晚上才出来. 本来实现一个功能,从数据库中查出一堆数据(比较多,几十万,不过,是分批查出来的),查出来的数据包含了一个XML字符串,代码中对其进行序列化,一 ...

  2. Apple Watch应用开发经验谈:我遇到的那些坑

    本文作者张忠良是滴答清单Apple Watch版应用的开发工程师,他用了一周的时间使用纯Objective-C语言完成了Apple Watch版滴答清单应用的开发工作.在这里,他从开发角度阐述了个人对 ...

  3. Emacs折腾经验谈

    Emacs折腾经验谈 这几天都没有动力写mongodb的东西,我果然还是太懒了么~ 主要是没有一个系统的东西整理出来,加上我令人拙计的语言表达能力,这个坑只能慢慢再补了. 最近在折腾emacs这个东西 ...

  4. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  5. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  6. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  7. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  8. 踩石行动:ViewPager无限轮播的坑

    2016-6-19 前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现.对于像我们常说的banner这样的效果,具备无限滑动的功能是可以用ViewPa ...

  9. 为C# as 类型转换及Assembly.LoadFrom埋坑!

    背景: 不久前,我发布了一个调试工具:发布:.NET开发人员必备的可视化调试工具(你值的拥有) 效果是这样的: 之后,有小部分用户反映,工具用不了(没反应或有异常)~~~ 然后,建议小部分用户换个电脑 ...

随机推荐

  1. 用c#开发微信 (6) 微渠道 - 推广渠道管理系统 1 基础架构搭建

    我们可以使用微信的“生成带参数二维码接口”和 “用户管理接口”,来实现生成能标识不同推广渠道的二维码,记录分配给不同推广渠道二维码被扫描的信息.这样就可以统计和分析不同推广渠道的推广效果. 本系统使用 ...

  2. MVC4+WebApi+Redis Session共享练习(下)

    上一篇文章我们主要讲解了一些webApi和redis缓存操作,这篇文章我们主要说一些MVC相关的知识(过滤器和错误处理),及采用ajax调用webApi服务. 本篇例子采用的开发环境为:VS2010( ...

  3. C++11 并发指南系列

    本系列文章主要介绍 C++11 并发编程,计划分为 9 章介绍 C++11 的并发和多线程编程,分别如下: C++11 并发指南一(C++11 多线程初探)(本章计划 1-2 篇,已完成 1 篇) C ...

  4. 学习WPF——WPF布局——初识布局容器

    StackPanel堆叠布局 StackPanel是简单布局方式之一,可以很方便的进行纵向布局和横向布局 StackPanel默认是纵向布局的 <Window x:Class="Wpf ...

  5. Mac下修改环境变量

    Mac下修改环境变量 如果使用默认Bash, 首先修改 ~/.bash_profile 文件,添加文件路径,比如: export PATH=~/bin:/usr/local/bin/node:~/Do ...

  6. Scala:条件表达式的好处

    条件表达式的好处之一是:让代码更简洁,例如在一个需要根据不同条件收集不同值的场景中,多数语言提供的代码如下: ; ) { tmp = xxx; } ) { tmp = yyy; } else { tm ...

  7. jenkins2 groovy语法

    文章来自:http://www.ciandcd.com 文中的代码来自可以从github下载: https://github.com/ciandcd   安装: wget https://dl.bin ...

  8. 浅谈压缩感知(二十九):压缩感知算法之迭代硬阈值(IHT)

    主要内容: 1.IHT的算法流程 2.IHT的MATLAB实现 3.二维信号的实验与结果 4.加速的IHT算法实验与结果 一.IHT的算法流程 文献:T. Blumensath and M. Davi ...

  9. Java程序员的日常——《编程思想》一切都是对象

    今天终于看完了第一章,哈哈,万事开头难....刚开始被编程思想的第一章给蒙住了,讲一堆理论,没什么意思.从第二章开始,真正的开始讲解Java相关的内容,有了一定的开发经验后,再次阅读起来,感觉收获良多 ...

  10. 安装完grunt和grunt-cli仍然无法识别grunt

    如题: 在安装完grunt-cli和grunt之后,仍然不识别grunt. 反复确认是-g安装... 原因: 有可能是nodejs安装出现问题,到时npm的路径没有出现在环境变量里面. 把C:\Use ...