86 votes

La sérialisation XML de l'interface de la propriété

Je voudrais XML sérialiser un objet qui a (entre autres) une propriété de type IModelObject (qui est une interface).

public class Example
{
    public IModelObject Model { get; set; }
}

Lorsque j'essaie de sérialiser un objet de cette classe, je reçois l'erreur suivante:
"Impossible de sérialiser membre de l'Exemple.Modèle de type Exemple parce que c'est une interface."

Je comprends que le problème est qu'une interface ne peut pas être sérialisé. Toutefois, le béton Modèle type d'objet n'est pas connu avant l'exécution.

Remplacement de la IModelObject interface avec un résumé ou d'un type de béton et d'utiliser l'héritage avec XMLInclude est possible, mais semble comme une vilaine solution de contournement.

Toutes les suggestions?

118voto

ShuggyCoUk Points 24204

C'est tout simplement l'une des limites inhérentes déclaratif de sérialisation où le type d'informations n'est pas incorporé à l'intérieur de la sortie.

En essayant de convertir <Flibble Foo="10" /> de retour dans

public class Flibble { public object Foo { get; set; } }

Comment le sérialiseur de savoir si elle doit être un entier, une chaîne, d'un lit double (ou autre chose)...

Pour ce faire, vous disposez de plusieurs options, mais si vous avez vraiment ne savez pas jusqu'à exécution la manière la plus simple pour ce faire est susceptible d'être à l'aide de la XmlAttributeOverrides.

Malheureusement, cela ne fonctionne qu'avec les classes de base, pas d'interfaces. Le meilleur que vous pouvez faire est d'ignorer la propriété qui n'est pas suffisant pour vos besoins.

Si vous devez vraiment rester avec les interfaces, vous avez trois options réelles:

Masquer et de traiter avec elle dans une autre propriété

Laid, désagréable chaudière plaque et beaucoup de répétitions, mais la plupart des consommateurs de la classe n'aura pas à régler le problème:

[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 of get and make an instance of the proper type*/ } 
}

Cela est susceptible de devenir un entretien cauchemar...

Mettre En Œuvre IXmlSerializable

Semblable à la première option que vous prenez le contrôle total de choses, mais

  • Pros
    • Vous n'avez pas méchant "faux" propriétés de traîner.
    • vous pouvez interagir directement avec la structure xml en ajoutant de la flexibilité et des versions
  • Cons
    • vous pouvez finir par avoir à re-mettre en œuvre la roue pour toutes les autres propriétés de la classe

Les problèmes de la duplication des efforts sont semblables à la première.

Modifier votre propriété à utiliser un type d'emballage

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); }
}

L'utilisation de ce impliquerait quelque chose comme (dans le projet 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);
    }
}

qui vous donne:

<?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>

C'est évidemment plus difficile pour les utilisateurs de la classe mais évite beaucoup de la chaudière de la plaque.

Un juste milieu peut être la fusion de la XmlAnything idée dans le "backing" propriété du la première technique. De cette façon, la plupart de la gros du travail est fait pour vous, mais les consommateurs de la classe souffrent pas d'impact au-delà de la confusion avec l'introspection.

43voto

Despertar Points 5365

La solution à ce problème est l'utilisation de la réflexion avec le DataContractSerializer. Vous n'avez même pas à marquer votre classe avec [DataContract] ou [DataMember]. Il va sérialiser un objet, indépendamment de savoir si il a une interface type de propriétés (y compris les dictionnaires) en xml. Ici est une simple extension de la méthode qui va sérialiser un objet XML, même si elle possède des interfaces (remarque vous pouvez le tordre pour exécuter de manière récursive).

    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());
    }

ce que l'expression LINQ n'est-il énumère chaque propriété, retours chaque propriété qui est une interface, obtient la valeur de la propriété (l'objet sous-jacent), obtient le type de l'objet concret il met dans un tableau, et l'ajoute à la sérialiseur de la liste des types connus.

Maintenant, le sérialiseur sait sur les types c'est la sérialisation de sorte qu'il puisse faire son travail.

3voto

csharptest.net Points 16556

Remplacement de la IModelObject interface avec un résumé ou d'un type de béton et d'utiliser l'héritage avec XMLInclude est possible, mais semble comme une vilaine solution de contournement.

Si il est possible d'utiliser une base abstraite, je recommanderais cet itinéraire. Il sera toujours plus propre que d'utiliser des roulés à la main de sérialisation. Le seul problème que je vois avec le résumé de base est que votre encore besoin de le type de béton? C'est du moins la façon dont je l'ai utilisé dans le passé, quelque chose comme:

	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;
	}

2voto

MattH Points 1737

Malheureusement il n'y a pas de réponse simple, comme le sérialiseur ne sais pas quoi sérialiser pour une interface. J'ai trouvé une explication plus complète sur la façon de contourner ce sur MSDN

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X