如何使用 XmlSerializer 序列化内部类?

Posted

技术标签:

【中文标题】如何使用 XmlSerializer 序列化内部类?【英文标题】:How can I serialize internal classes using XmlSerializer? 【发布时间】:2011-09-03 15:25:37 【问题描述】:

我正在构建一个库来与第三方交互。通信是通过 XML 和 HTTP Posts 进行的。那行得通。

但是,任何使用该库的代码都不需要了解内部类。我的内部对象使用这种方法序列化为 XML:

internal static string SerializeXML(Object obj)

    XmlSerializer serializer = new XmlSerializer(obj.GetType(), "some.domain");

    //settings
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    settings.OmitXmlDeclaration = true;

    using (StringWriter stream = new StringWriter())
    
        using (XmlWriter writer = XmlWriter.Create(stream, settings))
        
            serializer.Serialize(writer, obj);
        
        return stream.ToString();
    

但是,当我将类的访问修饰符更改为 internal 时,运行时出现异常:

[System.InvalidOperationException] = "MyNamespace.MyClass 由于其保护级别而无法访问。只能处理公共类型。"

该异常发生在上述代码的第一行。

我希望我的图书馆的课程不公开,因为我不想公开它们。我可以这样做吗?如何使用我的通用序列化程序使内部类型可序列化?我做错了什么?

【问题讨论】:

有时我忘记了 GC 并不能解决所有问题。我修复了代码并发布了它。 【参考方案1】:

来自Sowmy Srinivasan's Blog - Serializing internal types using XmlSerializer:

能够序列化内部类型是常见的请求之一 XmlSerializer 团队看到的。这是人们合理的要求 运输库。他们不想制作 XmlSerializer 类型 public 只是为了序列化程序。我最近从 将 XmlSerializer 写入使用的团队的团队 XmlSerializer。当我遇到类似的要求时,我说:“没办法。 使用DataContractSerializer"。

原因很简单。 XmlSerializer 通过生成代码来工作。这 生成的代码存在于动态生成的程序集中,需要 访问被序列化的类型。自从开发了 XmlSerializer 在lightweight code generation 出现之前的一段时间, 生成的代码不能访问除公共类型之外的任何内容 另一个大会。因此,被序列化的类型必须是公开的。

我听到精明的读者小声说:“它不必公开,如果 使用了“InternalsVisibleTo”属性”。

我说,“对,但生成的程序集的名称未知 前期。您使内部部件对哪个组件可见?"

精明的读者:“如果使用'sgen.exe',则程序集名称是已知的”

我:“要让 sgen 为您的类型生成序列化程序,它们必须是 公开”

精明的读者:“我们可以进行两次编译。一次通过 sgen 类型为 public 和另一个传递类型为 内部结构。”

他们可能是对的!如果我请精明的读者给我写一个样本 他们可能会写这样的东西。 (免责声明:这是 不是官方解决方案。 YMMV)

using System;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;
using System.Reflection;

[assembly: InternalsVisibleTo("Program.XmlSerializers")]

namespace InternalTypesInXmlSerializer

    class Program
    
        static void Main(string[] args)
        
            Address address = new Address();
            address.Street = "One Microsoft Way";
            address.City = "Redmond";
            address.Zip = 98053;
            Order order = new Order();
            order.BillTo = address;
            order.ShipTo = address;

            XmlSerializer xmlSerializer = GetSerializer(typeof(Order));
            xmlSerializer.Serialize(Console.Out, order);
        

        static XmlSerializer GetSerializer(Type type)
        
#if Pass1
            return new XmlSerializer(type);
#else
            Assembly serializersDll = Assembly.Load("Program.XmlSerializers");
            Type xmlSerializerFactoryType = serializersDll.GetType("Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializerContract");

            MethodInfo getSerializerMethod = xmlSerializerFactoryType.GetMethod("GetSerializer", BindingFlags.Public | BindingFlags.Instance);

            return (XmlSerializer)getSerializerMethod.Invoke(Activator.CreateInstance(xmlSerializerFactoryType), new object[]  type );

#endif
        
    

#if Pass1
    public class Address
#else
    internal class Address
#endif
    
        public string Street;
        public string City;
        public int Zip;
    

#if Pass1
    public class Order
#else
    internal class Order
#endif
    
        public Address ShipTo;
        public Address BillTo;
    
 

一些精明的“黑客”读者可能会给我 build.cmd 编译它。

csc /d:Pass1 program.cs

sgen program.exe

csc program.cs

【讨论】:

【参考方案2】:

作为替代方案,您可以使用动态创建的公共类(不会暴露给第 3 方):

static void Main()

    var emailType = CreateEmailType();

    dynamic email = Activator.CreateInstance(emailType);

    email.From = "x@xpto.com";
    email.To = "y@acme.com";
    email.Subject = "Dynamic Type";
    email.Boby = "XmlSerializer can use this!";


static Type CreateEmailType()

    var assemblyName = new AssemblyName("DynamicAssembly");

    var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

    var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);

    var typeBuilder = moduleBuilder.DefineType(
        "Email",
        (
            TypeAttributes.Public |
            TypeAttributes.Sealed |
            TypeAttributes.SequentialLayout |
            TypeAttributes.Serializable
        ),
        typeof(ValueType)
    );

    typeBuilder.DefineField("From", typeof(string), FieldAttributes.Public);
    typeBuilder.DefineField("To", typeof(string), FieldAttributes.Public);
    typeBuilder.DefineField("Subject", typeof(string), FieldAttributes.Public);
    typeBuilder.DefineField("Body", typeof(string), FieldAttributes.Public);

    return typeBuilder.CreateType();

【讨论】:

【参考方案3】:

这可能会对您有所帮助:MRB_ObjectSaver

此项目可帮助您将 c# 中的任何对象保存/加载/克隆到文件/字符串/从文件/字符串中保存/加载/克隆。与“c# 序列化”相比,此方法保持对对象的引用,并且对象之间的链接不会中断。 (示例见:SerializeObjectTest.cs)此外,类型没有被标记为[Serializable]

【讨论】:

欢迎提供解决方案链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.【参考方案4】:

您还可以使用称为 xgenplus (http://xge​​nplus.codeplex.com/) 的东西来生成通常会在运行时执行的代码。然后将其添加到您的解决方案中,并将其作为解决方案的一部分进行编译。此时,您的对象是否是内部的并不重要——您可以在同一个命名空间中添加预生成的代码。性能非常快,因为它都是预先生成的。

【讨论】:

您是否建议我使用日期为 2007 年 11 月 7 日的工具,下载次数为 12(十二)次? 不管怎样,让我问你这个问题:如果我要改变我的方法并使用这样的工具,我是否会有一个唯一的序列化代码或每个内部类都有一个序列化? @john:我认为它回答了这个问题——使用这个工具,类是否是内部的都没有关系,从而消除了这个问题。 @M.R. Q:“我如何使用工具 x 来做 y”,A:“不要使用工具 x,使用工具 z”。不是。 @john:这不是问题——问题是,“我试图用 Y 做 X,但它不起作用” A:“做 X,而不是 Y,但与 Z"。一个问题的答案不止一个,而直接的答案往往不是最合适的答案。

以上是关于如何使用 XmlSerializer 序列化内部类?的主要内容,如果未能解决你的问题,请参考以下文章

c# 对序列化类XMLSerializer 二次封装泛型化方便了一些使用的步骤

C# XmlSerializer 用不同的命名空间序列化同一个类

如何告诉 XmlSerializer 总是用 [DefautValue(...)] 序列化属性?

如何告诉 XmlSerializer 总是用 [DefautValue(...)] 序列化属性?

如何让 XmlSerializer 将布尔值编码为是/否?

Younge学习.NET反序列化漏洞