Как я могу сериализовать внутренние классы, используя XmlSerializer? - PullRequest
26 голосов
/ 27 мая 2011

Я создаю библиотеку для взаимодействия с третьей стороной. Связь осуществляется через 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, я получаю исключение в время выполнения :

* * 1010

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

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

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

Ответы [ 4 ]

19 голосов
/ 27 мая 2011

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

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

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

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

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

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

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

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

Они могут быть правы!Если я попрошу проницательных читателей написать мне образец, они, вероятно, напишут что-то вроде этого.(Отказ от ответственности: это не официальное решение. YMMV) * ​​1030 *

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
1 голос
/ 04 октября 2017

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

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();
}
0 голосов
/ 24 ноября 2017

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

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

0 голосов
/ 27 мая 2011

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

...