Come posso serializzare le classi interne usando XmlSerializer?

Sto costruendo una libreria da interfacciare con una terza parte. La comunicazione avviene tramite messaggi XML e HTTP. Funziona.

Ma qualsiasi codice usi la libreria non ha bisogno di essere a conoscenza delle classi interne. I miei oggetti interni sono serializzati su XML usando questo metodo:

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(); } } 

Tuttavia, quando modifico il modificatore di accesso delle mie classi su internal , ottengo un’eccezione in fase di runtime :

[System.InvalidOperationException] = {“MyNamespace.MyClass non è accessibile a causa del relativo livello di protezione. Possono essere elaborati solo tipi pubblici.”}

Tale eccezione si verifica nella prima riga del codice sopra.

    Vorrei che le classi della mia biblioteca non fossero pubbliche perché non voglio esporle. Posso farlo? Come posso rendere serializzabili i tipi interni, usando il mio serializzatore generico? Che cosa sto facendo di sbagliato?

    Dal Blog di Sowmy Srinivasan – Serializzare i tipi interni usando XmlSerializer :

    Essere in grado di serializzare i tipi interni è una delle richieste più comuni viste dal team XmlSerializer . È una richiesta ragionevole da parte delle biblioteche di spedizione. Non vogliono rendere pubblici i tipi XmlSerializer solo per il serializzatore. Recentemente mi sono trasferito dal team che ha scritto XmlSerializer a un team che utilizza XmlSerializer. Quando ho trovato una richiesta simile, ho detto ” Assolutamente no. Utilizza DataContractSerializer “.

    La ragione è semplice. XmlSerializer funziona generando codice. Il codice generato risiede in un assembly generato dynamicmente e deve accedere ai tipi serializzati. Poiché XmlSerializer è stato sviluppato in un momento precedente all’avvento della generazione di codice leggero , il codice generato non può accedere ad altro che ai tipi pubblici in un altro assieme. Quindi i tipi da serializzare devono essere pubblici.

    Sento astuti lettori sussurrare “Non deve essere pubblico se si utilizza l’attributo ‘ InternalsVisibleTo ‘”.

    Dico “Giusto, ma il nome dell’assemblaggio generato non è noto in anticipo. A quale assieme rendi visibili gli interni?”

    Astuti lettori: “il nome assembly è noto se si utilizza ‘ sgen.exe ‘”

    Io: “Per sgen generare serializzatori per i tuoi tipi, devono essere pubblici”

    Astuti lettori: “Potremmo fare una compilazione in due passaggi: un passaggio per sgen con tipi come pubblico e un altro per la spedizione con tipi come interni.”

    Potrebbero avere ragione! Se chiedo agli astuti lettori di scrivermi un campione, probabilmente scriverebbero qualcosa del genere. (Disclaimer: questa non è la soluzione ufficiale. 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; } } 

    Alcuni astuti lettori di “hacking” possono arrivare fino a darmi build.cmd per compilarlo.

     csc /d:Pass1 program.cs sgen program.exe csc program.cs 

    Una soluzione consiste nell’utilizzare un DataContractSerializer invece di un XmlSerializer.

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

    In alternativa puoi utilizzare classi pubbliche create dynamicmente (che non saranno esposte alla terza parte):

     static void Main() { var emailType = CreateEmailType(); dynamic email = Activator.CreateInstance(emailType); email.From = "[email protected]"; email.To = "[email protected]"; 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(); } 

    Questo può aiutarti: MRB_ObjectSaver

    Questo progetto ti aiuta a salvare / caricare / clonare qualsiasi object in c # a / da un file / stringa. In confronto a “serializzazione c #” questo metodo mantiene il riferimento agli oggetti e il collegamento tra gli oggetti non si interromperà. (vedi: SerializeObjectTest.cs per un esempio) Inoltre, il tipo non è stato contrassegnato come [Serializable]

    Puoi anche usare qualcosa chiamato xgenplus (http://xgenplus.codeplex.com/) che genera il codice che normalmente verrebbe eseguito in fase di runtime. Quindi lo aggiungi alla tua soluzione e la compila come parte della tua soluzione. A quel punto, non importa se il tuo object è interno – puoi aggiungere il codice pre-generato nello stesso spazio dei nomi. Le prestazioni per questo sono incredibilmente veloci, come tutte le pre-generate.