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