How can I serialize internal classes using XmlSerializer?

From Sowmy Srinivasan’s Blog – Serializing internal types using XmlSerializer:

Being able to serialize internal types is one of the common requests
seen by the XmlSerializer team. It is a reasonable request from people
shipping libraries. They do not want to make the XmlSerializer types
public just for the sake of the serializer. I recently moved from the
team that wrote the XmlSerializer to a team that consumes
XmlSerializer. When I came across a similar request I said, “No way.
Use DataContractSerializer“.

The reason is simple. XmlSerializer works by generating code. The
generated code lives in a dynamically generated assembly and needs to
access the types being serialized. Since XmlSerializer was developed
in a time before the advent of lightweight code generation, the
generated code cannot access anything other than public types in
another assembly. Hence the types being serialized has to be public.

I hear astute readers whisper “It does not have to be public if
InternalsVisibleTo‘ attribute is used”.

I say, “Right, but the name of the generated assembly is not known
upfront. To which assembly do you make the internals visible to?”

Astute readers : “the assembly name is known if one uses ‘sgen.exe‘”

Me: “For sgen to generate serializer for your types, they have to be
public”

Astute readers : “We could do a two pass compilation. One pass for
sgen with types as public and another pass for shipping with types as
internals.”

They may be right! If I ask the astute readers to write me a sample
they would probably write something like this. (Disclaimer: This is
not the official solution. 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;
    }
} 

Some astute ‘hacking’ readers may go as far as giving me the build.cmd
to compile it.

csc /d:Pass1 program.cs

sgen program.exe

csc program.cs

Leave a Comment