如何让XmlSerializer忽略IXmlSerializer实现以切换序列化模式
首先纠正一个笔误:你代码中写的IXmlSerializer是类型名错误,.NET 内置XML序列化场景下用于自定义逻辑的接口是System.Xml.Serialization.IXmlSerializable,以下方案均基于这个正确接口实现。
序列化逻辑切换方案
IXmlSerializable确实是全接管的机制——只要类实现了这个接口,XmlSerializer就会直接调用你写的ReadXml/WriteXml方法,不会自动回退到默认序列化逻辑。要实现按标志位切换,有两种成熟可行的方案:
方案1:在IXmlSerializable实现内部做分支(改动最小)
核心思路是利用线程静态变量控制分支,在不需要走自定义逻辑的时候,内部临时调用默认序列化器完成读写,同时加状态位避免递归调用。
首先给类加线程静态的开关(用[ThreadStatic]是为了避免多线程环境下开关值串扰),然后在ReadXml/WriteXml里判断开关值走对应逻辑:
using System.Xml; using System.Xml.Serialization; using System.Reflection; public class SomeClass : IXmlSerializable { // 线程静态开关,每个线程独立持有值,不会互相影响 [ThreadStatic] internal static bool UseCustomSerialization; // 你的其他业务属性 public string Name { get; set; } public int Count { get; set; } private ISomeDependency _dependency; // 必须保留无参构造函数,XmlSerializer 反射创建实例时依赖这个 public SomeClass() { } public SomeClass(ISomeDependency dependency) { _dependency = dependency; } public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { if (UseCustomSerialization) { // 这里写你的自定义反序列化逻辑,比如查库补全字段之类的 reader.ReadStartElement(); // 自定义节点读取逻辑... reader.ReadEndElement(); } else { // 临时关闭开关,避免递归调用自定义逻辑导致栈溢出 bool prevFlag = UseCustomSerialization; UseCustomSerialization = false; try { XmlSerializer defaultSerializer = new XmlSerializer(typeof(SomeClass)); SomeClass tempInstance = (SomeClass)defaultSerializer.Deserialize(reader); // 把默认序列化出来的临时实例属性拷贝到当前实例 foreach (PropertyInfo prop in typeof(SomeClass).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (prop.CanWrite) prop.SetValue(this, prop.GetValue(tempInstance)); } } finally { UseCustomSerialization = prevFlag; } } } public void WriteXml(XmlWriter writer) { if (UseCustomSerialization) { // 自定义序列化逻辑 } else { bool prevFlag = UseCustomSerialization; UseCustomSerialization = false; try { XmlSerializer defaultSerializer = new XmlSerializer(typeof(SomeClass)); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", ""); // 去掉冗余的命名空间 defaultSerializer.Serialize(writer, this, ns); } finally { UseCustomSerialization = prevFlag; } } } }
调用的时候只需要在反序列化前设置开关即可:
public static void Main(string[] args) { XmlReader rdr = null; // 你已初始化的XmlReader实例 XmlSerializer serializer = new XmlSerializer(typeof(SomeClass)); // 按需求设置开关 SomeClass.UseCustomSerialization = useCustomSerializer; SomeClass someClass = (SomeClass)serializer.Deserialize(rdr); }
方案2:拆分序列化模型(耦合最低)
如果两种序列化逻辑差异很大,不想把分支逻辑堆在同一个类里,可以拆分两个独立模型:
- 一个普通类不实现
IXmlSerializable,专门给默认序列化逻辑用 - 另一个类实现
IXmlSerializable,写完全自定义的序列化逻辑
反序列化完成后,用对象映射工具或者手动写映射逻辑把结果转成你需要的SomeClass实例即可。这个方案的好处是两种逻辑完全隔离,不会互相影响,缺点是需要写少量模型映射代码。
依赖注入实现方案
首先要明确一个硬限制:.NET 内置的XmlSerializer只会调用被序列化类的无参构造函数创建实例,没有内置对接DI容器的能力,你带依赖参数的构造函数不会被序列化器自动调用。根据你的依赖使用时机,可以选下面两种方案:
- 如果依赖不需要在
ReadXml执行过程中使用(也就是反序列化完成后才会用到依赖),直接在反序列化后手动注入即可:
这个方案侵入性最低,也符合DI的设计原则,是优先推荐的做法。// 给类加初始化方法 public void Init(ISomeDependency dependency) { _dependency = dependency; } // 调用逻辑 SomeClass someClass = (SomeClass)serializer.Deserialize(rdr); // 从你的DI容器中拿到依赖实例传入 someClass.Init(yourDependencyInstance); - 如果依赖必须在
ReadXml执行过程中使用(比如你示例里的查库逻辑是在反序列化时执行的),可以用轻量的服务定位器做中转:
注意不要滥用服务定位器,只在这种无法正常做构造函数注入的场景用就好。不要把依赖存成静态字段,除非你能确定依赖是全局线程安全的单例,否则很容易出现并发问题。// 静态服务持有类,只在序列化这种框架无法接入DI的场景使用 public static class ServiceLocator { private static IServiceProvider _provider; public static void Configure(IServiceProvider provider) => _provider = provider; public static T Resolve<T>() => (T)_provider.GetService(typeof(T)); } // 程序启动时初始化,把你构建好的DI容器传进去 // ServiceLocator.Configure(yourBuiltServiceProvider); // 在ReadXml里直接获取依赖 public void ReadXml(XmlReader rdr) { ISomeDependency dependency = ServiceLocator.Resolve<ISomeDependency>(); var someValues = dependency.GetSomeValuesFromDBForCustomSerializtion(); // 后续自定义逻辑 }
内容的提问来源于stack exchange,提问作者ANewGuyInTown
相关产品推荐
相关产品推荐

