Как я могу сериализовать внутренние классы с помощью XmlSerializer?

Я создаю библиотеку для взаимодействия с третьей стороной. Связь осуществляется через сообщения XML и HTTP. Это работает.

Но, независимо от того, какой код использует библиотеку, не нужно знать внутренние классы. С помощью этого метода мои внутренние объекты сериализуются в XML:

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

Однако, когда я изменяю модификатор доступа к классам на internal, я получаю исключение в время выполнения:

[System.InvalidOperationException] = { "MyNamespace.MyClass недоступен из-за уровня защиты. Можно обрабатывать только общедоступные типы." }

Это исключение происходит в первой строке вышеприведенного кода.

Я бы хотел, чтобы мои классы библиотеки не были общедоступными, потому что я не хочу их раскрывать. Я могу это сделать? Как я могу сделать внутренние типы сериализуемыми, используя мой универсальный сериализатор? Что я делаю неправильно?

Ответ 1

Из Блог Sowmy Srinivasan - Сериализация внутренних типов с помощью XmlSerializer:

Возможность сериализации внутренних типов является одним из распространенных запросов которую видит команда XmlSerializer. Это разумная просьба от людей библиотеки доставки. Они не хотят создавать типы XmlSerializer публично только ради сериализатора. Недавно я перешел из команда, которая написала XmlSerializer для команды, которая потребляет XmlSerializer. Когда я наткнулся на подобную просьбу, я сказал: "Нет. Используйте DataContractSerializer".

Причина проста. XmlSerializer работает, создавая код. сгенерированный код живет в динамически сгенерированной сборке и должен доступ к сериализуемым типам. Поскольку XmlSerializer был разработан за время до появления легкого генерации кода, сгенерированный код не может получить доступ к чему-либо, кроме другой сборке. Следовательно, типы, которые сериализуются, должны быть общедоступными.

Я слышу, как проницательные читатели шепчут: "Это не должно быть публично, если ' InternalsVisibleTo' используется атрибут.

Я говорю: "Правильно, но имя сгенерированной сборки неизвестно авансом. На какую сборку вы делаете внутренности видимыми? "

Астуальные читатели:" имя сборки известно, если вы используете " sgen.exe '"

Me: "Чтобы sgen генерировал сериализатор для ваших типов, они должны быть общественности"

Астуальные читатели: "Мы могли бы сделать компиляцию с двумя проходами. sgen с типами public и другой проход для доставки с типами Внутренности".

Они могут быть правы! Если я попрошу проницательных читателей написать мне образец они, вероятно, напишут что-то вроде этого. (Отказ от ответственности: это а не официальное решение. 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;
    }
} 

Некоторые проницательные "хакерские" читатели могут пойти так далеко, как дать мне build.cmd для его компиляции.

csc /d:Pass1 program.cs

sgen program.exe

csc program.cs

Ответ 3

В качестве альтернативы вы можете использовать динамически созданные общедоступные классы (которые не будут доступны третьему лицу):

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

Ответ 4

Это может вам помочь: MRB_ObjectSaver

Этот проект помогает вам сохранять/загружать/клонировать любой объект в С# в/из файла/строки. По сравнению с "сериализация С#" этот метод сохраняет ссылку на объекты, а связь между объектами не прерывается. (см. пример SerializeObjectTest.cs). Кроме того, этот тип не помечен как [Serializable]

Ответ 5

Вы также можете использовать что-то, называемое xgenplus (http://xgenplus.codeplex.com/), которое генерирует код, который обычно запускается во время выполнения. Затем вы добавляете это в свое решение и компилируете его как часть своего решения. В этот момент не имеет значения, является ли ваш объект внутренним - вы можете добавить сгенерированный код pre- в том же пространстве имен. Производительность для этого быстро растет, поскольку генерируется все pre-.