Comment puis-je sérialiser les classes internes en utilisant XmlSerializer?

je construis une bibliothèque pour interagir avec une tierce partie. La Communication se fait par le biais de messages XML et HTTP. Que de travail.

mais, quel que soit le code utilisé par la bibliothèque, il n'est pas nécessaire de connaître les classes internes. Mes objets internes sont sérialisés en XML en utilisant cette méthode:

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

cependant, lorsque je change le modificateur d'accès de mes classes en internal, j'obtiens une exception à exécution:

[Système.InvalidOperationException] = {"MyNamespace.MyClass est inaccessible en raison de son niveau de protection. Seuls les types publics peuvent être traités."}

Cette exception se produit dans la première ligne du code ci-dessus.

je voudrais que les cours de ma bibliothèque ne soient pas publics parce que je ne veux pas les exposer. Puis-je le faire? Comment puis-je rendre les types internes sérialisables, en utilisant mon serializer Générique? Ce que je fais mal?

23
demandé sur Mike Corcoran 2011-05-27 23:13:59

5 réponses

Sowmy Srinivasan Blog - la Sérialisation interne à l'aide de types de XmlSerializer:

pouvoir sérialiser les types internes est une des requêtes courantes vu par le XmlSerializer l'équipe. C'est une demande raisonnable de personnes bibliothèques d'expédition. Ils ne veulent pas faire les types XmlSerializer public juste pour le plaisir de le sérialiseur. J'ai récemment déménagé de l' équipe qui a écrit le XmlSerializer à une équipe qui consomme XmlSerializer. Quand je suis tombé sur une demande similaire, j'ai dit, "pas question. Utilisez DataContractSerializer".

La raison est simple. XmlSerializer fonctionne en générant du code. Le le code généré vit dans un assemblage généré dynamiquement et doit accédez aux types sérialisés. Depuis que XmlSerializer a été développé dans un temps avant l'avènement de l' génération de code léger, le le code généré ne peut pas accéder à autre chose que des types publics dans une autre assemblée. Par conséquent les types sérialisées doit être public.

j'entends des lecteurs astucieux chuchoter "il ne doit pas être public si ' interne Visibleto' attribut est utilisé".

je dis, "D'accord, mais le nom de l'assemblage généré n'est pas connu upfront. À quelle Assemblée rendez-vous les intérieurs visibles?"

lecteurs astucieux: "le nom de l'assemblée est connu si on utilise' sgen.exe'"

Me: "pour que sgen génère serializer pour vos types, ils doivent être public"

lecteurs astucieux: "nous pourrions faire une compilation en deux passages. Un laissez-passer pour sgen avec des types comme public et un autre pass pour l'expédition avec des types comme interne."

Ils ont peut-être raison! Si je demande aux lecteurs astucieux de m'écrire un échantillon ils écriraient probablement quelque chose comme ça. (Avertissement: Ce est pas la solution officielle. 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;
    }
} 

quelques astucieux' hacking ' lecteurs peuvent aller jusqu'à me donner la construction.cmd pour le compiler.

csc /d:Pass1 program.cs

sgen program.exe

csc program.cs
18
répondu hemp 2015-08-11 15:03:00

une solution consiste à utiliser un DataContractSerializer au lieu d'un XmlSerializer.

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

3
répondu Rami A. 2011-12-20 03:59:31

comme alternative, vous pouvez utiliser des classes publiques créées dynamiquement (qui ne seront pas exposées au tiers):

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();
}
1
répondu drowa 2017-10-04 02:40:38

Cela peut vous aider: MRB_ObjectSaver

Ce projet vous permet de sauvegarder/charger/cloner n'importe quel objet en c# à partir d'un fichier/chaîne. Dans comparer à "C # serialization" cette méthode garde la référence aux objets et le lien entre les objets ne se brisera pas. (voir: SerializeObjectTest.cs pour un exemple) de plus, le type n'a pas été marqué comme [Serialisable]

-2
répondu mrbm 2017-11-24 16:45:55

vous pouvez aussi utiliser quelque chose appelé xgenplus (http://xgenplus.codeplex.com/) génère le code qui serait normalement exécuté à l'exécution. Ensuite, vous ajoutez cela à votre solution et vous la compilez dans le cadre de votre solution. À ce moment - là, peu importe si votre objet est interne-vous pouvez ajouter le code pré-généré dans le même espace de noms. La performance pour cela est flambant rapide, comme son tout pré-généré.

-4
répondu M.R. 2011-05-27 19:37:19