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

时间:2011-05-27 19:14:00

标签: c# xml oop serialization xml-serialization

我正在建立一个与第三方接口的库。通过XML和HTTP帖子进行通信。那很有效。

但是,无论代码使用哪个库都不需要知道内部类。我使用此方法将内部对象序列化为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由于其保护级别而无法访问。只能处理公共类型。”}

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

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

5 个答案:

答案 0 :(得分:19)

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

  

能够序列化内部类型是常见的请求之一   由XmlSerializer团队看到。这是人们的合理要求   运输图书馆。他们不想制作XmlSerializer类型   公众只是为了序列化。我最近离开了   将XmlSerializer写入消费团队的团队   XmlSerializer的。当我遇到类似的请求时,我说:“没办法。   使用DataContractSerializer“。

     

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

     

我听到精明的读者低声说道“如果是这样的话,就不必公开   “InternalsVisibleTo”属性用于“。

     

我说,“没错,但生成的程序集的名称是未知的   前期。您可以将内部组件显示给哪个组件?“

     

敏锐的读者:“如果使用'sgen.exe'”

,则会知道程序集名称      

我:“为了让sgen为你的类型生成序列化器,它们必须是   公共“

     

精明的读者:“我们可以进行两遍编辑。一次通过   sgen类型为公共类型,另一个传递类型为   内部件“。

     

他们可能是对的!如果我请精明的读者给我写样品   他们可能会写这样的东西。 (免责声明:这是   不是官方解决方案。 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

答案 1 :(得分:3)

一种解决方法是使用DataContractSerializer而不是XmlSerializer。

另见:http://blogs.msdn.com/b/sowmy/archive/2008/10/04/serializing-internal-types-using-xmlserializer.aspx

答案 2 :(得分:1)

作为替代方案,您可以使用动态创建的公共类(不会向第三方公开):

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 :(得分:-2)

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

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

答案 4 :(得分:-4)

您还可以使用名为xgenplus(http://xge​​nplus.codeplex.com/)的内容生成通常在运行时执行的代码。然后将其添加到解决方案中,并将其作为解决方案的一部分进行编译。此时,如果您的对象是内部对象并不重要 - 您可以在同一名称空间中添加预生成的代码。这一点的表现非常快,因为它都是预生成的。