Dies ist einfach eine inhärente Einschränkung der deklarativen Serialisierung, bei der Typinformationen nicht in die Ausgabe eingebettet sind.
Beim Versuch, <Flibble Foo="10" />
wieder in umzuwandeln
public class Flibble { public object Foo { get; set; } }
Woher weiß der Serializer, ob es sich um ein Int, einen String, ein Double (oder etwas anderes) handeln soll?
Damit dies funktioniert, haben Sie mehrere Möglichkeiten. Wenn Sie es jedoch erst zur Laufzeit wirklich wissen, ist es wahrscheinlich am einfachsten, die XmlAttributeOverrides zu verwenden .
Leider funktioniert dies nur mit Basisklassen, nicht mit Schnittstellen. Das Beste, was Sie dort tun können, ist, die Eigenschaft zu ignorieren, die für Ihre Anforderungen nicht ausreicht.
Wenn Sie wirklich bei Schnittstellen bleiben müssen, haben Sie drei echte Optionen:
Verstecke es und kümmere dich in einer anderen Eigenschaft darum
Hässliche, unangenehme Kesselplatte und viel Wiederholung, aber die meisten Verbraucher der Klasse werden sich nicht mit dem Problem befassen müssen:
[XmlIgnore()]
public object Foo { get; set; }
[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized
{
get { /* code here to convert any type in Foo to string */ }
set { /* code to parse out serialized value and make Foo an instance of the proper type*/ }
}
Dies wird wahrscheinlich zu einem Alptraum für die Instandhaltung ...
Implementieren Sie IXmlSerializable
Ähnlich wie bei der ersten Option übernehmen Sie jedoch die volle Kontrolle über die Dinge
- Vorteile
- Sie haben keine bösen "falschen" Eigenschaften herumhängen.
- Sie können direkt mit der XML-Struktur interagieren und so Flexibilität / Versionierung hinzufügen
- Nachteile
- Möglicherweise müssen Sie das Rad für alle anderen Eigenschaften der Klasse erneut implementieren
Probleme der Doppelarbeit sind ähnlich wie beim ersten.
Ändern Sie Ihre Eigenschaft, um einen Umhüllungstyp zu verwenden
public sealed class XmlAnything<T> : IXmlSerializable
{
public XmlAnything() {}
public XmlAnything(T t) { this.Value = t;}
public T Value {get; set;}
public void WriteXml (XmlWriter writer)
{
if (Value == null)
{
writer.WriteAttributeString("type", "null");
return;
}
Type type = this.Value.GetType();
XmlSerializer serializer = new XmlSerializer(type);
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
serializer.Serialize(writer, this.Value);
}
public void ReadXml(XmlReader reader)
{
if(!reader.HasAttributes)
throw new FormatException("expected a type attribute!");
string type = reader.GetAttribute("type");
reader.Read(); // consume the value
if (type == "null")
return;// leave T at default value
XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
this.Value = (T)serializer.Deserialize(reader);
reader.ReadEndElement();
}
public XmlSchema GetSchema() { return(null); }
}
Dies zu verwenden würde ungefähr Folgendes beinhalten (in Projekt P):
public namespace P
{
public interface IFoo {}
public class RealFoo : IFoo { public int X; }
public class OtherFoo : IFoo { public double X; }
public class Flibble
{
public XmlAnything<IFoo> Foo;
}
public static void Main(string[] args)
{
var x = new Flibble();
x.Foo = new XmlAnything<IFoo>(new RealFoo());
var s = new XmlSerializer(typeof(Flibble));
var sw = new StringWriter();
s.Serialize(sw, x);
Console.WriteLine(sw);
}
}
was dir gibt:
<?xml version="1.0" encoding="utf-16"?>
<MainClass
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<RealFoo>
<X>0</X>
</RealFoo>
</Foo>
</MainClass>
Dies ist für Benutzer der Klasse offensichtlich umständlicher, vermeidet jedoch viel Kesselplatte.
Ein glückliches Medium kann darin bestehen, die XmlAnything-Idee in die Backing-Eigenschaft der ersten Technik zu integrieren. Auf diese Weise wird der größte Teil der Grunzarbeit für Sie erledigt, aber die Verbraucher der Klasse leiden nicht unter Auswirkungen, die über die Verwechslung mit der Selbstbeobachtung hinausgehen.
Die Lösung hierfür ist die Verwendung der Reflexion mit dem DataContractSerializer. Sie müssen Ihre Klasse nicht einmal mit [DataContract] oder [DataMember] markieren. Es wird jedes Objekt serialisieren, unabhängig davon, ob es Schnittstellentyp-Eigenschaften (einschließlich Wörterbücher) in XML hat. Hier ist eine einfache Erweiterungsmethode, mit der jedes Objekt in XML serialisiert wird, auch wenn es über Schnittstellen verfügt (beachten Sie, dass Sie dies optimieren können, um es auch rekursiv auszuführen).
Der LINQ-Ausdruck listet jede Eigenschaft auf, gibt jede Eigenschaft zurück, die eine Schnittstelle ist, ruft den Wert dieser Eigenschaft (des zugrunde liegenden Objekts) ab, ruft den Typ dieses konkreten Objekts ab, fügt ihn in ein Array ein und fügt ihn dem Serializer hinzu Liste bekannter Typen.
Jetzt weiß der Serializer, welche Typen er serialisiert, damit er seine Arbeit erledigen kann.
quelle
Sie können ExtendedXmlSerializer verwenden . Dieser Serializer unterstützt die Serialisierung von Schnittstelleneigenschaften ohne Tricks.
Ihre XML sieht folgendermaßen aus:
ExtendedXmlSerializer unterstützt .net 4.5 und .net Core.
quelle
Wenn Sie Ihre Schnittstellenimplementierer im Voraus kennen, gibt es einen ziemlich einfachen Hack, mit dem Sie Ihren Schnittstellentyp zur Serialisierung bringen können, ohne Parsing-Code zu schreiben:
Die resultierende XML sollte ungefähr so aussehen wie
quelle
Wenn es möglich ist, eine abstrakte Basis zu verwenden, würde ich diese Route empfehlen. Es ist immer noch sauberer als die handgerollte Serialisierung. Das einzige Problem, das ich mit der abstrakten Basis sehe, ist, dass Sie immer noch den konkreten Typ benötigen? Zumindest habe ich es in der Vergangenheit so benutzt, so etwas wie:
quelle
Leider gibt es keine einfache Antwort, da der Serializer nicht weiß, was er für eine Schnittstelle serialisieren soll. Ich habe eine ausführlichere Erklärung gefunden, wie dies auf MSDN umgangen werden kann
quelle
Leider hatte ich einen Fall, in dem die zu serialisierende Klasse Eigenschaften hatte, die auch Schnittstellen als Eigenschaften hatten, sodass ich jede Eigenschaft rekursiv verarbeiten musste. Außerdem wurden einige der Schnittstelleneigenschaften als [XmlIgnore] markiert, sodass ich diese überspringen wollte. Ich nahm Ideen, die ich in diesem Thread gefunden hatte, und fügte einige Dinge hinzu, um ihn rekursiv zu machen. Hier wird nur der Deserialisierungscode angezeigt:
quelle
In meinem Projekt habe ich eine
Liste <IFormatStyle> FormatStyleTemplates;
mit verschiedenen Typen.
Ich benutze dann die Lösung 'XmlAnything' von oben, um diese Liste verschiedener Typen zu serialisieren. Die generierte XML ist wunderschön.
quelle
Ich habe dank dieses Blogs hier eine einfachere Lösung gefunden (Sie benötigen den DataContractSerializer nicht): XML-Serialisierung abgeleiteter Typen, wenn sich der Basistyp in einem anderen Namespace oder einer anderen DLL befindet
Ein detailliertes Beispiel finden Sie hier auf MSDN: XmlSerializer Constructor (Type, extraTypesArray [])
Mir scheint, dass Sie für DataContracts oder Soap XMLs den XmlRoot überprüfen müssen, wie hier in dieser SO-Frage erwähnt .
Eine ähnliche Antwort gibt es hier auf SO, aber sie ist nicht als eine markiert, da es nicht das OP bereits in Betracht gezogen zu haben scheint.
quelle