Можно ли сериализовать внутренний сеттер объекта?

Есть ли способ сериализации свойства с внутренним установщиком в С#?
Я понимаю, что это может быть проблематично, но если есть способ - я хотел бы знать.

Пример:

[Serializable]
public class Person
{
    public int ID { get; internal set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

Код, который сериализует экземпляр класса Person:

Person person = new Person();
person.Age = 27;
person.Name = "Patrik";
person.ID = 1;

XmlSerializer serializer = new XmlSerializer(typeof(Person));
TextWriter writer = new StreamWriter(@"c:\test.xml");
serializer.Serialize(writer, person);
writer.Close();

Результат (отсутствует свойство ID):

<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Patrik</Name>
  <Age>27</Age>
</Person>

Ответ 1

Если это опция, DataContractSerializer (.NET 3.0) может сериализовать непубличные свойства:

[DataContract]
public class Person
{
    [DataMember]
    public int ID { get; internal set; }
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public int Age { get; set; }
}
...
static void Main()
{
    Person person = new Person();
    person.Age = 27;
    person.Name = "Patrik";
    person.ID = 1;

    DataContractSerializer serializer = new DataContractSerializer(typeof(Person));
    XmlWriter writer = XmlWriter.Create(@"c:\test.xml");
    serializer.WriteObject(writer, person);
    writer.Close();
}

С помощью xml (переформатированный):

<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://schemas.datacontract.org/2004/07/">
    <Age>27</Age>
    <ID>1</ID>
    <Name>Patrik</Name>
</Person>

Ответ 2

Вы можете реализовать IXmlSerializable, к сожалению это отрицает самое важное преимущество XmlSerializer (способность декларативно контролировать сериализацию). DataContractSerializer (на основе xml) и BinaryFormatter ( бинарные) могут использоваться как альтернативы XmlSerializer, каждый из которых имеет свои плюсы и минусы.

Ответ 3

Я думаю, что единственная альтернатива в один конец - реализовать IXmlSerializable и сделать запись объекта xml/разбор себя.

Изменить: После прочтения комментариев DataContractSerializer выглядит интересно;)

Ответ 4

Если вы выполняете сериализацию XML по умолчанию, он будет рассматривать только публичные свойства. Реализация IXmlSerializable позволит вам контролировать то, что сериализовано. Если вы выполняете "стандартную" сериализацию .NET, он будет рассматривать поля, а не свойства, поэтому ваш объект будет по-прежнему корректно сериализован без необходимости каких-либо дополнительных интерфейсов.

Ответ 5

Не то, что я нашел, не выполняя какую-то работу. Я считаю, что это потому, что созданный XmlSerializer использует отражение для генерации нового класса (который находится в новой сборке, поэтому не может видеть internal member/methods).

Может возникнуть пробег в использовании XmlSerialization PreCompilier для генерации кода, а затем для его изменения во внутренний класс для ваших целей, поэтому вы должны сделать что-то вроде:

XmlSerializer serializer = new MyPersonXmlSerializer();

Другим вариантом (и, вероятно, предпочтительным) является реализация IXmlSerializable, который будет направлять автоматически сгенерированный код для правильной работы.

Ответ 6

Это, безусловно, возможно. Я хочу проиллюстрировать решение с помощью XElement, которое, кстати, мне очень понравилось. Вам не нужно использовать XmlSerializer или DataContractSerializer или любые аннотации классов или свойств, например [DataContract] или [Serializable], если вы этого не хотите. Кроме того, приведенный ниже пример показывает, как вы можете обменять private set на internal set в моем примере, кстати:

using System;
using System.Linq;
using System.Xml.Linq;

namespace SerializationTesting
{

    class Person
    {

        // Notice how this object type uses private setters, something that the traditional XmlSerializer will complain about if you don't use a wrapper class..
        public string Name { get; private set; }
        public DateTime Birthday { get; private set; }
        public long HeightInMillimeters { get; private set; }
        public Gender Gendrality { get; private set; }

        // Generate a serialized XElement from this Person object.
        public XElement ToXElement()
        {
            return new XElement("person",
                new XAttribute("name", Name),
                new XAttribute("birthday", Birthday),
                new XAttribute("heightInMillimeters", HeightInMillimeters),
                new XAttribute("gendrality", (long)Gendrality)
            );
        }

        // Serialize this Person object to an XElement.
        public static Person FromXElement(XElement x)
        {
            return new Person(
                (string)x.Attribute("name"),
                (DateTime)x.Attribute("birthday"),
                (long)x.Attribute("heightInMillimeters"),
                (Gender)(long)x.Attribute("gendrality")
            );
        }

        public Person(string name, DateTime birthday, long heightInMillimeters, Gender gender)
        {
            Name = name;
            Birthday = birthday;
            HeightInMillimeters = heightInMillimeters;
            Gendrality = gender;
        }

        // You must override this in conjunction with overriding GetHashCode (below) if you want .NET collections (HashSet, List, etc.) to properly compare Person objects.
        public override bool Equals(object obj)
        {
            if (obj.GetType() == typeof(Person))
            {
                Person objAsPerson = (Person)obj;
                return Name == objAsPerson.Name && Birthday == objAsPerson.Birthday && HeightInMillimeters == objAsPerson.HeightInMillimeters && Gendrality == objAsPerson.Gendrality;
            }
            return false;
        }

        // You must override this in conjunction with overriding Equals (above) if you want .NET collections (HashSet, List, etc.) to properly compare Person objects.
        public override int GetHashCode()
        {
            return Name.GetHashCode() ^ Birthday.GetHashCode() ^ HeightInMillimeters.GetHashCode() ^ Gendrality.GetHashCode();
        }

        // This allows us to compare Person objects using the == operator.
        public static bool operator ==(Person a, Person b)
        {
            return a.Equals(b);
        }

        // This allows us to compate Person objects using the != operator.
        public static bool operator !=(Person a, Person b)
        {
            return !a.Equals(b);
        }
    }

    public enum Gender
    {
        Male,
        Female
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Create first person (note how UTC time saves and loads properly when casting).
            Person personOne = new Person("Alexandru", DateTime.UtcNow, 1000, Gender.Male);
            // Save the first person to a local file on the hard disk.
            personOne.ToXElement().Save("PersonOne.dat");
            // Create second person (not using UTC time this time around).
            Person personTwo = new Person("Alexandria", DateTime.Now, 900, Gender.Female);
            // Save the second person to a local file on the hard disk.
            personTwo.ToXElement().Save("PersonTwo.dat");
            // Load the first person from a local file on the hard disk.
            XDocument personOneDocument = XDocument.Load("PersonOne.dat");
            Person personOneLoadedFromDocument = Person.FromXElement(personOneDocument.Elements().First());
            // Load the second person from a local file on the hard disk.
            XDocument personTwoDocument = XDocument.Load("PersonTwo.dat");
            Person personTwoLoadedFromDocument = Person.FromXElement(personTwoDocument.Elements().First());
            // Serialize the first person to a string and then load them from that string.
            string personOneString = personOne.ToXElement().ToString();
            XDocument personOneDocumentFromString = XDocument.Parse(personOneString);
            Person personOneLoadedFromDocumentFromString = Person.FromXElement(personOneDocumentFromString.Elements().First());
            // Check for equalities between persons (all outputs will be "true").
            Console.WriteLine(personOne.Equals(personOneLoadedFromDocument));
            Console.WriteLine(personTwo.Equals(personTwoLoadedFromDocument));
            Console.WriteLine(personOne == personOneLoadedFromDocument);
            Console.WriteLine(personTwo == personTwoLoadedFromDocument);
            Console.WriteLine(personOne != personTwo);
            Console.WriteLine(personOneLoadedFromDocument != personTwoLoadedFromDocument);
            Console.WriteLine(personOne.Equals(personOneLoadedFromDocumentFromString));
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

Результат всех проверок равенства в приведенном выше приложении консоли будет true, как и ожидалось. Это не страдает от неприятностей, таких как необходимость отслеживать кодировки или способ анализа данных, потому что он делает все это для вас, и это не ограничивает ваш класс публичными сеттерами, как это делает XmlSerializer.