Приведу случай из реальной жизни. В системе, в разработке которой я принимаю участие, требуется довольно много разных данных сохранять в 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>. Т.е. при следующем объявлении класса Foo
class 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.
Комментариев нет:
Отправить комментарий