Код сериализации объектов
Tạo vào: 18 tháng 1, 2025
Tạo vào: 18 tháng 1, 2025
Breaking down the assignment
OK, let's see. The task involves demonstrating composition and aggregation, implementing inheritance, serializing data in various formats, and using XPath and LINQ for XML and JSON.
Thought about сериализация и десериализация для несколько секунд
Ниже приведён подробный разбор кода и пояснение к его работе, разбитое на логические блоки, чтобы вы могли понимать, что и где происходит.
Нужно продемонстрировать сериализацию и десериализацию объектов в разные форматы:
При этом один из членов класса должен быть закрыт от сериализации (запрещено сериализовать). В коде это поле Password
с атрибутом [NonSerialized]
.
Все сериализаторы (JSON, XML, SOAP и т.д.) должны реализовывать единый интерфейс (в коде это интерфейс ISerializer
), чтобы можно было легко подключать или заменять сериализаторы, не меняя основного кода.
Нужно выполнить:
В коде есть все необходимые части:
Country
с запрещённым к сериализации полем,CountryList
для удобства сериализации массива,JsonSerializer
, XmlSerializer
, SoapSerializer
(все имплементируют ISerializer
),Main
, где демонстрируется работа с этими сериализаторами и чтение/запись в файлы,ISerializer
public interface ISerializer { string Serialize(object obj); T Deserialize<T>(string data); }
object obj
) и возвращает строку (string
), где будет содержаться результат сериализации (например, JSON-строка, XML-строка, SOAP-строка).T
.Благодаря такому интерфейсу мы можем, не меняя основного кода, подставлять любой класс-сериализатор. Все классы, реализующие ISerializer
, будут иметь одинаковые методы (Serialize и Deserialize), но внутри них логика будет отличаться (JSON, XML, SOAP).
Country
с запрещённым полем[Serializable] public class Country { public string Name { get; set; } public int Rank { get; set; } [NonSerialized] // Запрет на сериализацию этого поля public string Password; public Country() { } // Пустой конструктор для десериализации public Country(string name, int number, string password) { Name = name; Rank = number; Password = password; } }
[Serializable]
говорит, что этот класс может участвовать в механизмe сериализации (в частности, используется SOAP- и Binary-сериализациями .NET).Password
помечено атрибутом [NonSerialized]
.XmlSerializer
и Json.NET
) чуть другие механизмы, но в нашем случае XmlSerializer
и JsonConvert
тоже не запишут это поле, поскольку для атрибута NonSerialized
(и при работе с XmlSerializer
, если поле не имеет get; set;
) оно чаще всего будет пропущено.[JsonIgnore]
, [XmlIgnore]
и т.д.CountryList
[Serializable] public class CountryList { public Country[] Countries { get; set; } // Использование массива вместо List public CountryList() { Countries = new Country[0]; } public static CountryList FromList(List<Country> countryList) { return new CountryList { Countries = countryList.ToArray() }; } public List<Country> ToList() { return new List<Country>(Countries); } }
List<Country>
).List<T>
.FromList
и ToList
помогают «конвертировать»:
List<Country>
в Country[]
(для удобства сериализации),Country[]
в List<Country>
(для удобства использования в коде).Json.NET
)public class JsonSerializer : ISerializer { public string Serialize(object obj) => JsonConvert.SerializeObject(obj); public T Deserialize<T>(string data) => JsonConvert.DeserializeObject<T>(data); }
JsonConvert.SerializeObject(obj)
, который превращает объект в JSON-строку.JsonConvert.DeserializeObject<T>(data)
, который из JSON-строки создаёт обратно объект указанного типа T
.Обратите внимание, что мы используем библиотеку Newtonsoft.Json
(подключается обычно через NuGet).
System.Xml.Serialization.XmlSerializer
)public class XmlSerializer : ISerializer { public string Serialize(object obj) { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(obj.GetType()); var stringWriter = new StringWriter(); xmlSerializer.Serialize(stringWriter, obj); return stringWriter.ToString(); } public T Deserialize<T>(string data) { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(T)); var stringReader = new StringReader(data); return (T)xmlSerializer.Deserialize(stringReader); } }
В Serialize:
XmlSerializer
(класс из System.Xml.Serialization
), которому передаётся тип сериализуемого объекта.StringWriter
, куда XmlSerializer
запишет результат в XML-формате.В Deserialize:
XmlSerializer
на тип T
.StringReader
для чтения XML-строки.Deserialize
, который из XML-строки «собирает» объект заданного типа.public class SoapSerializer : ISerializer { public string Serialize(object obj) { using (var memoryStream = new MemoryStream()) { IFormatter formatter = new SoapFormatter(); formatter.Serialize(memoryStream, obj); return Convert.ToBase64String(memoryStream.ToArray()); } } public T Deserialize<T>(string data) { var bytes = Convert.FromBase64String(data); using (var memoryStream = new MemoryStream(bytes)) { IFormatter formatter = new SoapFormatter(); return (T)formatter.Deserialize(memoryStream); } } }
SoapFormatter
.MemoryStream
.SoapFormatter
.memoryStream
.Convert.ToBase64String(...)
. Это нужно, чтобы удобно было выводить/хранить сериализованные данные в виде «текстовой строки».Convert.FromBase64String
, чтобы получить исходный массив байтов.MemoryStream
.SoapFormatter
метод Deserialize
и приводим результат к типу T
.Main
и основная демонстрацияpublic static void Main() { // Создаём список стран var countryList = new List<Country> { new Country("Russia", 10, "secret_password"), new Country("Belarus", 20, "another_password"), new Country("China", 5, "mypassword") }; // Преобразуем список в класс-обертку var wrappedCountryList = CountryList.FromList(countryList); // Используем SOAP сериализатор ISerializer soapSerializer = new SoapSerializer(); string soapData = soapSerializer.Serialize(wrappedCountryList); Console.WriteLine("SOAP Serialized: " + soapData); // Запись SOAP в файл File.WriteAllText("countrys.soap", soapData); // Десериализация коллекции var deserializedCountryList = soapSerializer.Deserialize<CountryList>(soapData); Console.WriteLine("Deserialized Countries from SOAP:"); // Печатаем результат десериализации foreach (var country in deserializedCountryList.ToList()) { Console.WriteLine($"Name: {country.Name}, Rank: {country.Rank}, Password: {country.Password}"); } ... }
Создаётся список List<Country>
с тремя странами. Каждая страна имеет поля:
Name
("Russia", "Belarus", "China"),Rank
(10, 20, 5),Password
("secret_password", "another_password", "mypassword").Оборачиваем список countryList
в объект CountryList
(метод FromList
), поскольку для SOAP-сериализации (и вообще для части сериализаторов) удобнее работать с классом, а не напрямую со списком.
Soap-сериализация:
SoapSerializer
.soapSerializer.Serialize(wrappedCountryList)
, результат кладём в строку soapData
.countrys.soap
.Soap-десериализация:
soapData
(либо могли бы взять из файла).deserializedCountryList = soapSerializer.Deserialize<CountryList>(soapData)
.CountryList
.deserializedCountryList.ToList()
превращаем его в List<Country>
.Password
(если SOAP учёл атрибут [NonSerialized]
) будет null
или пустым (зависит от поведения при десериализации).Далее аналогично идёт демонстрация JSON и XML сериализации:
// Используем JSON сериализатор ISerializer jsonSerializer = new JsonSerializer(); string jsonData = jsonSerializer.Serialize(countryList); Console.WriteLine("JSON Serialized: " + jsonData); // Запись JSON в файл File.WriteAllText("countrys.json", jsonData); // Чтение JSON из файла и десериализация string jsonFromFile = File.ReadAllText("countrys.json"); var deserializedPeopleJson = jsonSerializer.Deserialize<List<Country>>(jsonFromFile); Console.WriteLine("Deserialized from JSON:"); foreach (var countrie in deserializedPeopleJson) { Console.WriteLine($"{countrie.Name}, {countrie.Rank}"); }
wrappedCountryList
, а просто List<Country>
. С Json.NET
можно делать и так, никаких проблем.countrys.json
, потом оттуда читаем.// Используем XML сериализатор ISerializer xmlSerializer = new XmlSerializer(); string xmlData = xmlSerializer.Serialize(countryList); Console.WriteLine("XML Serialized: " + xmlData); // Запись XML в файл File.WriteAllText("countrys.xml", xmlData); // Чтение XML из файла и десериализация string xmlFromFile = File.ReadAllText("countrys.xml"); var deserializedPeopleXml = xmlSerializer.Deserialize<List<Country>>(xmlFromFile); Console.WriteLine("Deserialized from XML:"); foreach (var countrie in deserializedPeopleXml) { Console.WriteLine($"{countrie.Name}, {countrie.Rank}"); }
XmlSerializer
.countrys.xml
, чтение оттуда, вывод.После того как мы продемонстрировали сериализацию/десериализацию, идёт пример вставки XML вручную и работы с ним через XPath.
string filePath = "countrys.xml"; using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) using (var writer = new StreamWriter(fs, new System.Text.UnicodeEncoding())) { writer.Write("<?xml version=\"1.0\" encoding=\"UTF-16\"?>"); writer.Write("<Countrys>"); writer.Write("<Country>"); writer.Write("<Name>Russia</Name>"); writer.Write("<Rank>10</Rank>"); writer.Write("<Password>secret_password</Password>"); writer.Write("</Country>"); writer.Write("</Countrys>"); }
Здесь код принудительно записывает XML в файл countrys.xml
, заменяя то, что было до этого.
Получается следующая структура:
<?xml version="1.0" encoding="UTF-16"?> <Countrys> <Country> <Name>Russia</Name> <Rank>10</Rank> <Password>secret_password</Password> </Country> </Countrys>
Далее идёт код:
XDocument xdoc; try { xdoc = XDocument.Load(filePath); var countryNames = xdoc.XPathSelectElements("//Country/Name"); Console.WriteLine("Все названия стран:"); foreach (var name in countryNames) { Console.WriteLine(name.Value); } var countriesWithHighRank = xdoc.XPathSelectElements("//Country[Rank > 5]/Name"); Console.WriteLine("\nСтраны с рангом больше 5:"); foreach (var name in countriesWithHighRank) { Console.WriteLine(name.Value); } } catch (XmlException ex) { Console.WriteLine("Ошибка при загрузке XML: " + ex.Message); }
XDocument.Load(filePath)
– загружает XML-документ из файла.XPathSelectElements("//Country/Name")
– находит все элементы <Name>
внутри <Country>
в любом месте XML (путь //Country/Name
).name.Value
для каждого найденного элемента – получаем «Russia».XPathSelectElements("//Country[Rank > 5]/Name")
– находит все <Country>
с условием, что значение <Rank>
внутри них больше 5, и у этих элементов берёт <Name>
.
Rank = 10
для Russia, это удовлетворяет Rank > 5
.Далее идёт код:
var states = new XElement("Countrys", new XElement("Country", new XElement("Name", "Russia"), new XElement("Rank", 10), new XElement("Password", "secret_password")), new XElement("Country", new XElement("Name", "Belarus"), new XElement("Rank", 20), new XElement("Password", "another_password")), new XElement("Country", new XElement("Name", "China"), new XElement("Age", 5), new XElement("Password", "mypassword") ) ); states.Save("countrys.xml");
XElement
), которая будет иметь корень <Countrys>
и внутри три <Country>
.China
для демонстрации вставлено поле <Age>
вместо <Rank>
(скорее всего, чтобы показать, что можно произвольно изменять структуру).states
в файл countrys.xml
.var names = from state in states.Elements("Country") select state.Element("Name").Value; Console.WriteLine("\nAll names:"); foreach (var name in names) { Console.WriteLine(name); } var worserThan15 = from state in states.Elements("Country") let rank = (int)state.Element("Rank") where rank > 15 select state.Element("Name").Value; Console.WriteLine("Rank worser than 15:"); foreach (var name in worserThan15) { Console.WriteLine(name); }
from state in states.Elements("Country")
– берём все дочерние элементы <Country>
из корневого <Countrys>
.select state.Element("Name").Value
– берём значение <Name>
для каждой страны.
rank = (int)state.Element("Rank")
.China
вместо <Rank>
стоит <Age>
, поэтому для него state.Element("Rank")
вернёт null
, и при попытке (int)null
будет исключение (в реальном коде нужно обрабатывать или проверять, что элемент не null). Но тут демонстрационный пример.where rank > 15
— выбираем только те, у кого Rank
больше 15. По структуре XML это будет у «Belarus» (Rank = 20
).ISerializer
позволяет подменять разные реализации сериализаторов (JSON, XML, SOAP) без изменения остального кода.Country
– демонстрирует атрибут [NonSerialized]
на поле Password
, которое (в классической сериализации .NET, например SOAP или Binary) не будет сохранено.CountryList
– служит удобной обёрткой для массивов (или списков), чтобы проще передавать их в методы сериализаторов (особенно для SOAP- и XmlSerializer
).Main
:
Country
.countrys.soap
),countrys.json
),countrys.xml
).Password
при десериализации исчезает (или null), тем самым показав запрет на сериализацию.XDocument
и XPath:
//Country/Name
или //Country[Rank > 5]/Name
.XElement
) и выполнять к нему LINQ-запросы, похожие на SQL по синтаксису (from ... select ...).Это даёт полное решение по части задания: есть сериализация в несколько форматов, есть пример работы с XML через XPath и LINQ to XML/JSON, а также продемонстрирован механизм исключения поля (Password
) из сериализации.
SoapSerializer
, только использовать BinaryFormatter
(но в более новых версиях .NET она уже не рекомендуется).XmlSerializer
и/или Json.NET
можно было бы дополнительно использовать [XmlIgnore]
и [JsonIgnore]
.Password
не является свойством с { get; set; }
, а лишь обычным public string
, оно часто уже не будет сериализовываться XmlSerializer’ом. Но тут могут быть тонкости.SOAP
поле Password
точно не будет сериализовано благодаря [NonSerialized]
.Таким образом, код выполняет все основные пункты задания:
CountryList
и Country
).Password
показан через атрибут [NonSerialized]
.JsonConvert
),XmlSerializer
).List<Country>
) сериализуются и десериализуются, сохраняются в файлы.