Jest to po prostu nieodłączne ograniczenie serializacji deklaratywnej, w której informacje o typie nie są osadzone w danych wyjściowych.
Podczas próby konwersji z <Flibble Foo="10" />
powrotem do
public class Flibble { public object Foo { get; set; } }
Skąd serializator wie, czy powinien to być int, string, double (lub coś innego) ...
Aby to zadziałało, masz kilka opcji, ale jeśli naprawdę nie wiesz do czasu wykonania, najłatwiejszym sposobem na to jest użycie XmlAttributeOverrides .
Niestety będzie to działać tylko z klasami bazowymi, a nie interfejsami. Najlepsze, co możesz zrobić, to zignorować właściwość, która nie jest wystarczająca dla Twoich potrzeb.
Jeśli naprawdę musisz pozostać przy interfejsach, masz trzy prawdziwe opcje:
Ukryj to i zajmij się nim w innej nieruchomości
Brzydka, nieprzyjemna płyta kotłowa i dużo powtórzeń, ale większość konsumentów tej klasy nie będzie musiała radzić sobie z problemem:
[XmlIgnore()]
public object Foo { get; set; }
[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized
{
get { }
set { }
}
To prawdopodobnie stanie się koszmarem związanym z konserwacją ...
Zaimplementuj IXmlSerializable
Podobnie jak w przypadku pierwszej opcji, w której przejmujesz pełną kontrolę nad rzeczami, ale
- Plusy
- Nie masz w pobliżu paskudnych „fałszywych” nieruchomości.
- możesz współdziałać bezpośrednio ze strukturą XML, dodając elastyczność / wersjonowanie
- Cons
- może się okazać, że będziesz musiał ponownie zaimplementować koło dla wszystkich innych właściwości w klasie
Kwestie powielania wysiłków są podobne do pierwszego.
Zmodyfikuj właściwość, aby użyć typu zawijania
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();
if (type == "null")
return;
XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
this.Value = (T)serializer.Deserialize(reader);
reader.ReadEndElement();
}
public XmlSchema GetSchema() { return(null); }
}
Użycie tego wymagałoby czegoś takiego (w projekcie 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);
}
}
co daje:
<?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>
Jest to oczywiście bardziej uciążliwe dla użytkowników tej klasy, ale pozwala uniknąć zbytniej płyty kotłowej.
Wesołym medium może być scalanie pomysłu XmlAnything z właściwością „backing” pierwszej techniki. W ten sposób większość podstawowej pracy jest wykonywana za Ciebie, ale konsumenci tej klasy nie mają żadnego wpływu poza pomyłką z introspekcją.
Rozwiązaniem tego problemu jest użycie odbicia z DataContractSerializer. Nie musisz nawet oznaczać swojej klasy za pomocą [DataContract] lub [DataMember]. Będzie serializować dowolny obiekt, niezależnie od tego, czy ma właściwości typu interfejsu (w tym słowniki) do XML. Oto prosta metoda rozszerzenia, która spowoduje serializację dowolnego obiektu do XML, nawet jeśli ma on interfejsy (pamiętaj, że możesz to zmienić, aby również działał rekurencyjnie).
public static XElement ToXML(this object o) { Type t = o.GetType(); Type[] extraTypes = t.GetProperties() .Where(p => p.PropertyType.IsInterface) .Select(p => p.GetValue(o, null).GetType()) .ToArray(); DataContractSerializer serializer = new DataContractSerializer(t, extraTypes); StringWriter sw = new StringWriter(); XmlTextWriter xw = new XmlTextWriter(sw); serializer.WriteObject(xw, o); return XElement.Parse(sw.ToString()); }
Wyrażenie LINQ wylicza każdą właściwość, zwraca każdą właściwość, która jest interfejsem, pobiera wartość tej właściwości (obiekt bazowy), pobiera typ tego konkretnego obiektu, umieszcza go w tablicy i dodaje ją do lista znanych typów.
Teraz serializator wie, jakie typy serializuje, aby mógł wykonać swoją pracę.
źródło
Jeśli znasz z góry implementatorów interfejsu, możesz skorzystać z dość prostego hacka, aby uzyskać typ interfejsu do serializacji bez pisania kodu parsującego:
public interface IInterface {} public class KnownImplementor01 : IInterface {} public class KnownImplementor02 : IInterface {} public class KnownImplementor03 : IInterface {} public class ToSerialize { [XmlIgnore] public IInterface InterfaceProperty { get; set; } [XmlArray("interface")] [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))] [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))] [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))] public object[] InterfacePropertySerialization { get { return new[] { InterfaceProperty }; ; } set { InterfaceProperty = (IInterface)value.Single(); } } }
Wynikowy plik XML powinien wyglądać podobnie do
<interface><ofTypeKnownImplementor01><!-- etc... -->
źródło
Możesz użyć ExtendedXmlSerializer . Ten serializator obsługuje serializację właściwości interfejsu bez żadnych sztuczek.
var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create(); var obj = new Example { Model = new Model { Name = "name" } }; var xml = serializer.Serialize(obj);
Twój xml będzie wyglądał następująco:
<?xml version="1.0" encoding="utf-8"?> <Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples"> <Model exs:type="Model"> <Name>name</Name> </Model> </Example>
ExtendedXmlSerializer obsługuje .net 4.5 i .net Core.
źródło
Jeśli można skorzystać z abstrakcyjnej bazy, to poleciłbym tę trasę. Nadal będzie czystszy niż przy użyciu serializacji ręcznej. Jedynym problemem, jaki widzę z abstrakcyjną podstawą, jest to, że nadal będziesz potrzebować konkretnego typu? Przynajmniej tak go używałem w przeszłości, coś takiego:
public abstract class IHaveSomething { public abstract string Something { get; set; } } public class MySomething : IHaveSomething { string _sometext; public override string Something { get { return _sometext; } set { _sometext = value; } } } [XmlRoot("abc")] public class seriaized { [XmlElement("item", typeof(MySomething))] public IHaveSomething data; }
źródło
Niestety nie ma prostej odpowiedzi, ponieważ serializator nie wie, co serializować dla interfejsu. Znalazłem pełniejsze wyjaśnienie, jak obejść ten problem w witrynie MSDN
źródło
Niestety, miałem przypadek, w którym klasa do serializacji miała właściwości, które miały również interfejsy jako właściwości, więc musiałem rekurencyjnie przetwarzać każdą właściwość. Ponadto niektóre właściwości interfejsu zostały oznaczone jako [XmlIgnore], więc chciałem je pominąć. Wziąłem pomysły, które znalazłem w tym wątku i dodałem do niego kilka rzeczy, aby był rekurencyjny. Pokazany jest tylko kod deserializacji:
void main() { var serializer = GetDataContractSerializer<MyObjectWithCascadingInterfaces>(); using (FileStream stream = new FileStream(xmlPath, FileMode.Open)) { XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas()); var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader); // your code here } } DataContractSerializer GetDataContractSerializer<T>() where T : new() { Type[] types = GetTypesForInterfaces<T>(); // Filter out duplicates Type[] result = types.ToList().Distinct().ToList().ToArray(); var obj = new T(); return new DataContractSerializer(obj.GetType(), types); } Type[] GetTypesForInterfaces<T>() where T : new() { return GetTypesForInterfaces(typeof(T)); } Type[] GetTypesForInterfaces(Type T) { Type[] result = new Type[0]; var obj = Activator.CreateInstance(T); // get the type for all interface properties that are not marked as "XmlIgnore" Type[] types = T.GetProperties() .Where(p => p.PropertyType.IsInterface && !p.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Any()) .Select(p => p.GetValue(obj, null).GetType()) .ToArray(); result = result.ToList().Concat(types.ToList()).ToArray(); // do the same for each of the types identified foreach (Type t in types) { Type[] embeddedTypes = GetTypesForInterfaces(t); result = result.ToList().Concat(embeddedTypes.ToList()).ToArray(); } return result; }
źródło
Znalazłem prostsze rozwiązanie (nie potrzebujesz DataContractSerializer), dzięki temu blogowi tutaj: serializacja XML typów pochodnych, gdy typ podstawowy znajduje się w innej przestrzeni nazw lub DLL
XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});
Szczegółowy przykład znajduje się tutaj w witrynie MSDN: XmlSerializer Constructor (Type, extraTypesArray [])
Wydaje mi się, że w przypadku DataContracts lub Soap XML trzeba sprawdzić XmlRoot, jak wspomniano tutaj w tym pytaniu SO .
Podobna odpowiedź jest tutaj na SO , ale to nie jest oznaczony jako jeden, a nie PO wydaje się uznać go już.
źródło
w moim projekcie mam
List <IFormatStyle> FormatStyleTemplates;
zawierające różne typy.
Następnie używam rozwiązania „XmlAnything” z góry, aby serializować tę listę różnych typów. Wygenerowany xml jest piękny.
[Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlArray("FormatStyleTemplates")] [XmlArrayItem("FormatStyle")] public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML { get { return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray(); } set { // read the values back into some new object or whatever m_FormatStyleTemplates = new FormatStyleProvider(null, true); value.ForEach(t => m_FormatStyleTemplates.Add(t.Value)); } }
źródło