2009-07-02 4 views
3

Я пытаюсь десериализовать пользовательский класс с помощью XmlSerializer и имею несколько проблем, в том, что я не знаю типа, который я собираюсь десериализовать (он подключается), и я «Трудно определить его.C# Custom Xml Serialization

Я нашел this post, который выглядит аналогичным, но не может заставить его работать с моим подходом, потому что мне нужно десериализовать интерфейс, который является XmlSerializable.

У меня в настоящее время есть та часть формы. Обратите внимание, что я ожидаю и должен иметь возможность обрабатывать как класс A, так и класс B, которые будут реализованы через плагин. Поэтому, если я могу избежать использования IXmlSerializable (что, я думаю, не могу), это было бы здорово.

ReadXml for A - это то, за что я застрял. Однако, если есть другие изменения, которые я могу внести для улучшения системы, я буду рад сделать это.

public class A : IXmlSerializable 
{ 
    public IB MyB { get; set;} 

    public void ReadXml(System.Xml.XmlReader reader) 
    { 
     // deserialize other member attributes 

     SeekElement(reader, "MyB"); 
     string typeName = reader.GetAttribute("Type"); 

     // Somehow need to the type based on the typename. From potentially 
     //an external assembly. Is it possible to use the extra types passed 
     //into an XMlSerializer Constructor??? 
     Type bType = ??? 

     // Somehow then need to deserialize B's Members 
     // Deserialize X 
     // Deserialize Y 
    } 

    public void WriteXml(System.Xml.XmlWriter writer) 
    { 
     // serialize other members as attributes 

     writer.WriteStartElement("MyB"); 
     writer.WriteAttributeString("Type", this.MyB.GetType().ToString()); 
     this.MyB.WriteXml(writer); 
     writer.WriteEndElement(); 
    } 

    private void SeekElement(XmlReader reader, string elementName) 
    { 
     ReaderToNextNode(reader); 
     while (reader.Name != elementName) 
     { 
     ReaderToNextNode(reader); 
     } 
    } 

    private void ReaderToNextNode(XmlReader reader) 
    { 
     reader.Read(); 
     while (reader.NodeType == XmlNodeType.Whitespace) 
     { 
     reader.Read(); 
     } 
    } 
} 

public interface IB : IXmlSerializable 
{ 
} 

public class B : IB 
{ 

    public void ReadXml(XmlReader reader) 
    { 
     this.X = Convert.ToDouble(reader.GetAttribute("x")); 
     this.Y = Convert.ToDouble(reader.GetAttribute("y")); 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("x", this.X.ToString()); 
     writer.WriteAttributeString("y", this.Y.ToString()); 
    } 
} 

ПРИМЕЧАНИЕ: Обновлено, поскольку я понял, что B должен был использовать интерфейс IB. Извините за неверный вопрос.

ответ

4

Чтобы создать экземпляр из строки, используйте одну из перегрузок Activator.CreateInstance. Чтобы просто получить тип с этим именем, используйте Type.GetType.

+0

Это интересный подход. Итак, вы предлагаете в этот момент вызвать ReadXml() на созданном типе для инициализации членов? Это может работать в зависимости от того, могу ли я получить тип + создать один из возможно внешнего типа в моей библиотеке. – Ian

+0

Я отвечал на ваш комментарий, где вы сказали: «Как-то нужно, чтобы тип был основан на имени типа». –

+0

Спасибо, Джон. Прочитав тип из XML, создав экземпляр, я смог де-сериализовать его с помощью метода ReadXml созданного экземпляра. Работает с удовольствием и отлично работает с моим интерфейсом. – Ian

0

Я бы воспользовался xpath, чтобы быстро выяснить, содержит ли вход xml класс A или класс B. Затем десериализуйте его на основе этого.

+0

booo для xpath :( –

+0

хе-хе, эй, я тоже junkie, хотя я остался в стороне от более экзотических особенностей xpath ... :) –

+0

Не могли бы вы привести очень краткий пример? Я не использовал xpath. Просто интересно, стоит ли смотреть. Хотелось иметь дело с XML как можно меньше. – Ian

1

Я не думаю, что вам нужно реализовать IXmlSerializable ...

Поскольку вы не знаете, фактические типы до выполнения, вы можете динамически добавлять атрибут переопределяет к XmlSerializer. Вам просто нужно знать список типов, которые наследуются от A. Например, если вы используете в качестве свойства другого класса:

public class SomeClass 
{ 
    public A SomeProperty { get; set; } 
} 

Вы можете динамически применять XmlElementAttribute с для каждого производного типа к этому свойству:

XmlAttributes attr = new XmlAttributes(); 
var candidateTypes = from t in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()) 
        where typeof(A).IsAssignableFrom(t) && !t.IsAbstract 
        select t; 
foreach(Type t in candidateTypes) 
{ 
    attr.XmlElements.Add(new XmlElementAttribute(t.Name, t)); 
} 

XmlAttributeOverrides overrides = new XmlAttributeOverrides(); 
overrides.Add(typeof(SomeClass), "SomeProperty", attr); 

XmlSerializer xs = new XmlSerializer(typeof(SomeClass), overrides); 
... 

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

+0

Томас, это похоже на ответ Маркса в другом посте, на который я ссылался, и, скорее всего, это идея для меня. Проблема, однако, заключается в свойстве A.MyB (я обновил вопрос, чтобы сделать его более понятным). Когда я попытался выполнить сериализацию этого раньше, я получаю сообщение об ошибке, потому что MyB фактически является интерфейсом. Есть ли способ объединить сериализацию/десериализацию этого интерфейса в предлагаемом вами подходе? – Ian

+1

Нет, к сожалению, сериализация XML не сериализует интерфейсы ... в этом случае я боюсь, что вам придется использовать IXmlSerializable. –

+0

Да, я так думал ... Я начинаю задаваться вопросом, может быть, мой интерфейс должен быть абстрактным классом. Я предполагаю, что тогда это сработает, а иерархии классов (например, B-реализация AbstractB) будут десериализованы, а не пытаться десериализовать AbstractB напрямую. – Ian