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