2012-04-20 24 views
8

Tengo una clase que tiene una propiedad que se define como interfaz. Los usuarios de mi clase pueden asignar a esta propiedad cualquier implementación de clase que implemente la interfaz. Quiero poder cargar este estado de clase desde un archivo de texto en el disco. Los usuarios deberían poder modificar manualmente el archivo xml para controlar el funcionamiento de la aplicación.Cómo serializar el miembro con tipo de interfaz

Si trato de serializar mi clase, me dice que no puedo serializar una interfaz. Entiendo que el serializador no tiene conocimiento sobre la estructura de la clase de la propiedad, solo sabe que implementa una interfaz.

Hubiera esperado que llamara GetType al miembro y reflejara la estructura de la clase real. ¿Hay una manera de lograr esto? ¿Hay alguna otra forma de implementar mi requerimiento?

Editar: Aclaración de mis intenciones: Digamos que tengo esta clase:

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

y el usuario clase tiene una clase con

Cuando serializar el miCarro clase, espera que el xml se asemeje a esto:

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

Respuesta

2

basado en la solución @Jens he creado un serializador que hace lo que necesito. Gracias Jen. Aquí está el código:

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

Tal vez podría usar una clase base en lugar de una interfaz y serializar eso.

actualización

me di cuenta de que el uso de una clase base no era realmente una opción para usted.

La mejor solución sería probablemente hacer una solución con un DTO como dijo Henk Holterman.

Pero si realmente quieres una solución para tu pregunta, creo que tendrías que crear tu propio serializador personalizado, pero no lo recomendaría porque terminarías con muchos errores que resolver.

Aquí hay un ejemplo de un serializador personalizado, tenga en cuenta que este ejemplo será que necesita algún trabajo para ser utilizado por completo en una aplicación real.

al menos dos cosas tiene que ser añadido para que esto funcione para algo más que un ejemplo:

  1. manipulación
  2. fundición o convertir el valor de elemento XML para corregir tipo en la línea Excepción 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

Esto funcionará, con '[XmlInclude()]' para anunciar los tipos. –

+0

Básicamente, quería evitar el uso de la clase base porque las interfaces parecían más correctas. Además, no sé de antemano qué tipo de concreto puede usar el usuario. (Entiendo que necesito declararlos en XmlInclude). ¿Puedo evitar tener que saber con anticipación los tipos de clases que el usuario podría usar? – itaysk

+0

@Henk, OK Creo que ahora entiendo la pregunta, sí, eso implicaría usar XmlInclude; esto haría que mi sugerencia fuera poco valiosa. –

7

Puede marcar la propiedad como no-incluir.

Sin embargo, hay un problema más profundo: la serialización solo puede capturar un "estado" simple, no el comportamiento. Tu clase no es del tipo serializable. ¿Qué 'valor' esperas que tenga la propiedad después de la deserialización? null es la única opción.

La solución correcta sería pensar en lo que en realidad debería ser guardar y utilizar un DTO para esa parte.


El siguiente modelo se puede serializar:

public class BaseEngine { } 

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

No me gustaría marcarlo como do-not-include porque lo necesito serializado :). Usted pregunta "¿Qué 'valor' espera que tenga la propiedad después de la deserialización? _" - El mismo valor que existía antes de la serialización. (Explicado en mi pregunta) – itaysk

+1

"mismo valor que existía antes de la serialización" - ¿Y la instancia de implementación también está serializada? –

+0

@HenkHolterman Windows 8 IE 10 ... accidente. Lo siento, estoy tratando de deshacerlo. Si editas la publicación, puedo cambiarla ... Lo siento. –

1

Puede utilizar ExtendedXmlSerializer. Si usted tiene unos clases:

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

y crear instancia de esta clase:

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

puede serializar este objeto usando ExtendedXmlSerializer:

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

XML de salida se verá así:

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

Puede instalar ExtendedXmlSerializer desde nuget o ejecuta el comando siguiente:

Install-Package ExtendedXmlSerializer 

Aquí es online example

Cuestiones relacionadas