2012-04-20 3 views
8

Ho una classe che ha una proprietà definita come interfaccia. Gli utenti della mia classe possono assegnare a questa proprietà qualsiasi implementazione di classe che implementa l'interfaccia. Voglio essere in grado di caricare questo stato di classe da un file di testo su disco. Gli utenti dovrebbero essere in grado di modificare manualmente il file xml, al fine di controllare l'operazione dell'applicazione.Come serializzare l'elemento digitato nell'interfaccia

Se provo a serializzare la mia classe, mi dice che non posso serializzare un'interfaccia. Capisco che il serializzatore non abbia conoscenza della struttura della classe della proprietà, sapendo solo che implementa un'interfaccia.

Mi sarei aspettato che chiamasse GetType sul membro e riflettesse nella struttura della classe effettiva. C'è un modo per ottenere questo? C'è un altro modo per implementare il mio requisito?

Edit: Chiarire le mie intenzioni: Diciamo che ho questa classe:

class Car 
{ 
IEngine engine 
} 
class ElectricEngine : IEngine 
{ 
int batteryPrecentageLeft; 
} 
class InternalCombustionEngine : IEngine 
{ 
int gasLitersLeft; 
} 

e l'utente classe ha una classe con

Quando ho serializzare l'myCar classe, ho si aspetta che l'xml assomigli a questo:

<Car> 
<Engine> 
<ElectricEngine> 
<batteryPrecentageLeft>70</batteryPrecentageLeft> 
</ElectricEngine> 
<Engine> 
</Car> 

risposta

2

Sulla base di soluzione @Jens ho creato un serializzatore che fa quello che mi serve. Grazie Jen. ecco il codice:

public class RuntimeXmlSerializerAttribute : XmlIgnoreAttribute { } 

public class RuntimeXmlSerializer 
{ 
    private Type m_type; 
    private XmlSerializer m_regularXmlSerializer; 

    private const string k_FullClassNameAttributeName = "FullAssemblyQualifiedTypeName"; 

    public RuntimeXmlSerializer(Type i_subjectType) 
    { 
     this.m_type = i_subjectType; 
     this.m_regularXmlSerializer = new XmlSerializer(this.m_type); 
    } 

    public void Serialize(object i_objectToSerialize, Stream i_streamToSerializeTo) 
    { 
     StringWriter sw = new StringWriter(); 
     this.m_regularXmlSerializer.Serialize(sw, i_objectToSerialize); 
     XDocument objectXml = XDocument.Parse(sw.ToString()); 
     sw.Dispose(); 
     SerializeExtra(i_objectToSerialize,objectXml); 
     string res = objectXml.ToString(); 
     byte[] bytesToWrite = Encoding.UTF8.GetBytes(res); 
     i_streamToSerializeTo.Write(bytesToWrite, 0, bytesToWrite.Length); 
    } 

    public object Deserialize(Stream i_streamToSerializeFrom) 
    { 
     string xmlContents = new StreamReader(i_streamToSerializeFrom).ReadToEnd(); 
     StringReader sr; 
     sr = new StringReader(xmlContents); 
     object res = this.m_regularXmlSerializer.Deserialize(sr); 
     sr.Dispose(); 
     sr = new StringReader(xmlContents); 
     XDocument doc = XDocument.Load(sr); 
     sr.Dispose(); 
     deserializeExtra(res, doc); 
     return res; 
    } 

    private void deserializeExtra(object i_desirializedObject, XDocument i_xmlToDeserializeFrom) 
    { 
     IEnumerable propertiesToDeserialize = i_desirializedObject.GetType() 
      .GetProperties().Where(p => p.GetCustomAttributes(true) 
       .FirstOrDefault(a => a.GetType() == 
        typeof(RuntimeXmlSerializerAttribute)) != null); 
     foreach (PropertyInfo prop in propertiesToDeserialize) 
     { 
      XElement propertyXml = i_xmlToDeserializeFrom.Descendants().FirstOrDefault(e => 
       e.Name == prop.Name); 
      if (propertyXml == null) continue; 
      XElement propertyValueXml = propertyXml.Descendants().FirstOrDefault(); 
      Type type = Type.GetType(propertyValueXml.Attribute(k_FullClassNameAttributeName).Value.ToString()); 
      XmlSerializer srl = new XmlSerializer(type); 
      object deserializedObject = srl.Deserialize(propertyValueXml.CreateReader()); 
      prop.SetValue(i_desirializedObject, deserializedObject, null); 
     } 
    } 

    private void SerializeExtra(object objectToSerialize, XDocument xmlToSerializeTo) 
    { 
     IEnumerable propertiesToSerialize = 
      objectToSerialize.GetType().GetProperties().Where(p => 
       p.GetCustomAttributes(true).FirstOrDefault(a => 
        a.GetType() == typeof(RuntimeXmlSerializerAttribute)) != null); 
     foreach (PropertyInfo prop in propertiesToSerialize) 
     { 
      XElement serializedProperty = new XElement(prop.Name); 
      serializedProperty.AddFirst(serializeObjectAtRuntime(prop.GetValue(objectToSerialize, null))); 
      xmlToSerializeTo.Descendants().First().Add(serializedProperty); //TODO 
     } 
    } 

    private XElement serializeObjectAtRuntime(object i_objectToSerialize) 
    { 
     Type t = i_objectToSerialize.GetType(); 
     XmlSerializer srl = new XmlSerializer(t); 
     StringWriter sw = new StringWriter(); 
     srl.Serialize(sw, i_objectToSerialize); 
     XElement res = XElement.Parse(sw.ToString()); 
     sw.Dispose(); 
     XAttribute fullClassNameAttribute = new XAttribute(k_FullClassNameAttributeName, t.AssemblyQualifiedName); 
     res.Add(fullClassNameAttribute); 

     return res; 
    } 
} 
5

Forse potresti usare una classe base invece di un'interfaccia e serializzarla.

Aggiornamento

mi sono reso conto che l'utilizzo di una classe di base non era davvero un'opzione per voi.

La soluzione migliore sarebbe probabilmente quella di fare una soluzione con un DTO come ha affermato Henk Holterman.

Ma se vuoi davvero una soluzione per la tua domanda penso che dovresti creare il tuo serializzatore personalizzato, ma non lo consiglierei perché si finirebbe con un sacco di bug da risolvere.

Ecco un esempio per un serializzatore personalizzato, tenere presente che questo esempio sarà necessario un po 'di lavoro da utilizzare pieno in un'applicazione reale.

Almeno due cose deve essere aggiunta per far funzionare per più di un semplice esempio:

  1. movimentazione
  2. Casting o convertendo il valore dell'elemento XML per correggere tipo sulla linea eccezione anyThingProperty.SetValue(obj, propertyElement.Value, null);
[TestClass] 
public class SerializableInterfaceTest 
{ 
    [TestMethod] 
    public void TestMethod1() 
    { 
     string serialize = AnyThingSerializer.Serialize(
      new SerializableClass {Name = "test", Description = "test1", 
       AnyThing = new Animal {Name = "test", Color = "test1"}}); 
     Console.WriteLine(serialize); 
     object obj = AnyThingSerializer.Deserialize(serialize); 
    } 
} 

public sealed class SerializableClass 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 

    [AnyThingSerializer] 
    public object AnyThing { get; set; } 
} 

public static class AnyThingSerializer 
{ 
    public static string Serialize(object obj) 
    { 
     Type type = obj.GetType(); 
     var stringBuilder = new StringBuilder(); 
     var serializer = new XmlSerializer(type); 
     serializer.Serialize(new StringWriter(stringBuilder), obj); 
     XDocument doc = XDocument.Load(new StringReader(stringBuilder.ToString())); 
     foreach (XElement xElement in SerializeAnyThing(obj)) 
     { 
      doc.Descendants().First().Add(xElement); 
     } 
     return doc.ToString(); 
    } 

    public static object Deserialize(string xml) 
    { 
     var serializer = new XmlSerializer(typeof (T)); 
     object obj = serializer.Deserialize(new StringReader(xml)); 
     XDocument doc = XDocument.Load(new StringReader(xml)); 
     DeserializeAnyThing(obj, doc.Descendants().OfType().First()); 
     return obj; 
    } 

    private static void DeserializeAnyThing(object obj, XElement element) 
    { 
     IEnumerable anyThingProperties = obj.GetType() 
      .GetProperties().Where(p => p.GetCustomAttributes(true) 
       .FirstOrDefault(a => a.GetType() == 
        typeof (AnyThingSerializerAttribute)) != null); 
     foreach (PropertyInfo anyThingProperty in anyThingProperties) 
     { 
      XElement propertyElement = element.Descendants().FirstOrDefault(e => 
       e.Name == anyThingProperty.Name && e.Attribute("type") != null); 
      if (propertyElement == null) continue; 
      Type type = Type.GetType(propertyElement.Attribute("type").Value); 
      if (IsSimpleType(type)) 
      { 
       anyThingProperty.SetValue(obj, propertyElement.Value, null); 
      } 
      else 
      { 
       object childObject = Activator.CreateInstance(type); 
       DeserializeAnyThing(childObject, propertyElement); 
       anyThingProperty.SetValue(obj, childObject, null); 
      } 
     } 
    } 

    private static List SerializeAnyThing(object obj) 
    { 
     var doc = new List(); 
     IEnumerable anyThingProperties = 
      obj.GetType().GetProperties().Where(p => 
       p.GetCustomAttributes(true).FirstOrDefault(a => 
        a.GetType() == typeof (AnyThingSerializerAttribute)) != null); 
     foreach (PropertyInfo anyThingProperty in anyThingProperties) 
     { 
      doc.Add(CreateXml(anyThingProperty.Name, 
       anyThingProperty.GetValue(obj, null))); 
     } 
     return doc; 
    } 

    private static XElement CreateXml(string name, object obj) 
    { 
     var xElement = new XElement(name); 
     Type type = obj.GetType(); 
     xElement.Add(new XAttribute("type", type.AssemblyQualifiedName)); 
     foreach (PropertyInfo propertyInfo in type.GetProperties()) 
     { 
      object value = propertyInfo.GetValue(obj, null); 
      if (IsSimpleType(propertyInfo.PropertyType)) 
      { 
       xElement.Add(new XElement(propertyInfo.Name, value.ToString())); 
      } 
      else 
      { 
       xElement.Add(CreateXml(propertyInfo.Name, value)); 
      } 
     } 
     return xElement; 
    } 

    private static bool IsSimpleType(Type type) 
    { 
     return type.IsPrimitive || type == typeof (string); 
    } 
} 

public class AnyThingSerializerAttribute : XmlIgnoreAttribute 
{ 
} 
+0

Questo funziona, con '[XmlInclude()]' di annunciare i tipi. –

+0

Fondamentalmente volevo evitare l'uso della classe base perché le interfacce sembravano più giuste. Inoltre, non so in anticipo quale tipo di tipi di calcestruzzo l'utente potrebbe utilizzare. (Capisco che ho bisogno di dichiarare quelli in XmlInclude). Posso evitare di sapere in anticipo i tipi di classi che l'utente potrebbe usare? – itaysk

+0

@Henk, OK Penso di aver capito la domanda ora, sì che implicherebbe l'uso di XmlInclude; questo renderebbe il mio suggerimento un po 'inutile. –

7

È possibile contrassegnare la proprietà come mancata inclusione.

C'è un problema più profondo però: la serializzazione può solo acquisire semplice 'stato', non comportamento. La tua classe non è del tipo serializzabile. Quale "valore" ti aspetti che la proprietà abbia dopo la deserializzazione? null è l'unica opzione.

La soluzione corretta sarebbe pensare a Cosa dovrebbe essere effettivamente salvare e utilizzare un DTO per quella parte.


il seguente modello può essere serializzato:

public class BaseEngine { } 

[XmlInclude(typeof(InternalCombustionEngine))] 
[XmlInclude(typeof(ElectricEngine))] 
public class Car 
{  
    public BaseEngine Engine { get; set; } 
} 
+0

Non vorrei contrassegnarlo come non-includere perché ne ho bisogno serializzato :). Si chiede "_Che 'valore' si aspetta che la proprietà abbia dopo la deserializzazione? _" - Lo stesso valore che era prima della serializzazione. (spiegato nella mia domanda) – itaysk

+1

"stesso valore che era prima della serializzazione" - E l'istanza di implementazione è anche serializzata? –

+0

@HenkHolterman Windows 8 IE 10 ... incidente. Scusa, sto cercando di annullarlo. Se modifichi il post, posso cambiarlo ... Scusa. –

1

È possibile utilizzare ExtendedXmlSerializer. Se avete un classi:

public interface IEngine 
{ 
    string Name {get;set;} 
} 

public class Car 
{ 
    public IEngine Engine {get;set;} 
} 


public class ElectricEngine : IEngine 
{ 
    public string Name {get;set;} 
    public int batteryPrecentageLeft {get;set;} 
} 

public class InternalCombustionEngine : IEngine 
{ 
    public string Name {get;set;} 
    public int gasLitersLeft {get;set;} 
} 

e creare un'istanza di questa classe:

Car myCar = new Car(); 
myCar.Engine = new ElectricEngine() {batteryPrecentageLeft= 70, Name = "turbo diesel"}; 

è possibile serializzare l'oggetto utilizzando ExtendedXmlSerializer:

ExtendedXmlSerializer serializer = new ExtendedXmlSerializer(); 
var xml = serializer.Serialize(myCar); 

output XML sarà simile:

<?xml version="1.0" encoding="utf-8"?> 
<Car type="Program+Car"> 
    <Engine type="Program+ElectricEngine"> 
     <Name>turbo diesel</Name> 
     <batteryPrecentageLeft>70</batteryPrecentageLeft> 
    </Engine> 
</Car> 

È possibile installare ExtendedXmlSerializer da nuget o eseguire il seguente comando:

Install-Package ExtendedXmlSerializer 

Ecco online example