Приведу случай из реальной жизни. В системе, в разработке которой я принимаю участие, требуется довольно много разных данных сохранять в xml. Поначалу мы много использовали xml сериализацию. Требовалось уметь сериализовывать и десериализовывать 5 или 6 типов графов. Единственное, что было общее у этих графов - необходимость сериализации в файл, в строку. Вполне естевственно, что после появления второй иерархии объектов для сериализации, возникло желание использовать общий код для сериализации и десериализации графов. Приходила в голову идея использовать внешнюю утилиту, однако использование внешней утилиты показалось не очень красивым (требовалась передача типа корня сериализации во внешний метод):
Foo foo = XmlUtils.DeserializeFromFile(typeof(Foo), "Foo.xml");или
Foo foo = XmlUtils.DeserializeFromFile<Foo>("Foo.xml");а душе хотелось полета:Foo foo = Foo.DeserializeFromFile("Foo.xml");Таким образом, требуется определить статический метод, который бы для типа Foo возвращал результат типа Foo, а для типа Bar возвращал бы результат типа Bar. Есть средство. Определим хитрый базовый класс:public class XmlSerializationRoot<TRoot>
where TRoot : XmlSerializationRoot<TRoot>
{
public static TRoot DeserializeFrom(TextReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
return (TRoot)CreateSerializer().Deserialize(reader);
}
public static TRoot DeserializeFrom(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
using (StreamReader reader = new StreamReader(stream))
return DeserializeFrom(reader);
}
public static TRoot DeserializeFromString(string xml)
{
using (TextReader reader = new StringReader(xml))
return DeserializeFrom(reader);
}
public static TRoot DeserializeFromFile(string fileName)
{
using (TextReader reader = File.OpenText(fileName))
return DeserializeFrom(reader);
}
private static XmlSerializer CreateSerializer()
{
return new XmlSerializer(typeof(TRoot));
}
public void SerializeTo(TextWriter writer)
{
if (writer == null)
throw new ArgumentNullException("writer");
CreateSerializer().Serialize(writer, this);
}
public string SerializeToString()
{
StringBuilder builder = new StringBuilder();
using (StringWriter writer = new StringWriter(builder))
this.SerializeTo(writer);
return builder.ToString();
}
public void SerializeToFile(string fileName)
{
using (StreamWriter writer = File.CreateText(fileName))
this.SerializeTo(writer);
}
public void SerializeTo(Stream stream)
{
using (StreamWriter writer = new StreamWriter(stream))
this.SerializeTo(writer);
}
}Особый интерес представляют статические методы десериализации. Методы сериализации были внесены до кучи, чтобы пример был полным. Вообще говоря, класс XmlSerializationRoot не будет являться базовым классом для классов с возможностью сериализации. Базовым классом для них будет XmlSerializationRoot<T>. Т.е. при следующем объявлении класса Fooclass Foo : XmlSerializationRoot<Foo>
{
}мы получим ситуацию, где класс Foo наследует определенные у класса XmlSerializationRoot<Foo> методы. Теперь можно пользоваться этими методами через идентификатор типа Foo:Foo foo = Foo.DeserializeFromFile("foo.xml");В качестве завершающего штриха предлагаю выделить интерфейсpublic interface IXmlSerializable
{
void SerializeTo(TextWriter writer);
void SerializeToFile(string fileName);
void SerializeTo(Stream stream);
string SerializeToString();
}и поддержать его классом XmlSerializationRoot. Теперь мы сможем вызывать методы сериализации не зная типа сохраняемого объекта. Теперь буду писать гадости про этот подход. - Данный подход навязывает ограничение наследования на разрабатываемые классы. При необходимости наследования классов от другого класса, использование усложняется, однако, можно объявить собственные методы и делегировать их вспомогательному классу. Например:
class Foo : SomeBaseClass // наследуемся от чего-то другого { public static Foo DeserializeFromFile(string fileName) { return XmlSerializationRoot<Foo>.DeserializeFromFile(fileName); } }В данном случае потребуется убрать constraint у класса XmlSerializationRoot. В принципе, он объявлен формально для контроля за способом использования функциональности, и не требуется для компиляции методов класса.
- Resharper ругается на использование методов базового класса через идентификатор производного типа. Можно подкрутить его настройки, вставить управляющий комментарий, либо просто игнорировать данные сообщения.
- Пользуясь этим подходом мы не можем прочитать что-то из файла, а потом разобраться, что это было. Но в этом виноват не только сей подход, а так же устройство XML сериализации в FCL.
- Если требуется объявить только статические методы, то мы не сможем объявить базовый класс с модификатором static. Если объявим, то не сможем унаследовать от него.
P.S. Хочу обратить внимание, что в данном посте не шла речь о корректности данного подхода к сериализации в архитектурном плане. Это лишь пример издевательства над языком. Однако, данный код отлично работал и был удобно используемым, пока мы не перешли на более тяжелый (но более гибкий) способ сериализации через XmlDocument.
Комментариев нет:
Отправить комментарий