La publicación de Robert Rossney me hizo pensar que probablemente sea menos trabajo de lo que pensaba. Así que aquí hay un intento muy duro.Se ocupa de lo siguiente:
- Si no es capaz de leer una propiedad, se imprime la excepción, ya que el valor
- Referencias cíclicas y múltiples ocurrencias. Asocia una identificación con cada elemento; si un elemento aparece dos veces, solo señala la ID de la referencia. El Id. De referencia es exclusivo del gráfico de objetos (probablemente debería usar un GUID, pero esto se ajusta a mis propósitos).
- No tiene problemas con tipos derivados
- No requiere de atributos o constructores específicos u otro sentido
- Puede manejar propiedades de sólo lectura-
He aquí un ejemplo de la salida (en mis objetos de prueba , el producto "Moneda" en la Orden arroja una excepción).
<Customer Ref="1">
<FirstName>Paul</FirstName>
<LastName>Stovell</LastName>
<FullName>Paul Stovell</FullName>
<Orders>
<Order Ref="2">
<SKU>Apples</SKU>
<Price>27.30</Price>
<Currency>Something bad happened</Currency>
<Customer Ref="1" />
</Order>
<Order Ref="3">
<SKU>Pears</SKU>
<Price>17.85</Price>
<Currency>Something bad happened</Currency>
<Customer Ref="1" />
</Order>
<Order Ref="2" />
</Orders>
</Customer>
Aquí está el modelo de objetos de la muestra y su uso:
static void Main(string[] args)
{
var customer = new Customer();
customer.FirstName = "Paul";
customer.LastName = "Stovell";
customer.Orders.Add(new Order(customer) { Price = 27.30M, SKU = "Apples"});
customer.Orders.Add(new Order(customer) { Price = 17.85M, SKU = "Pears"});
customer.Orders.Add(customer.Orders[0]);
var output = new StringWriter();
var writer = new XmlTextWriter(output);
writer.Formatting = Formatting.Indented;
WriteComplexObject("Customer", customer, writer);
Console.WriteLine(output.ToString());
Console.ReadKey();
}
class Customer
{
private readonly List<Order> _orders = new List<Order>();
public Customer()
{
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
// Read-only property test
get { return FirstName + " " + LastName; }
}
public List<Order> Orders
{
// Collections test
get { return _orders; }
}
}
class Order
{
private readonly Customer _customer;
public Order(Customer customer)
{
_customer = customer;
}
public string SKU { get; set; }
public decimal Price { get; set; }
public string Currency
{
// A proprty that, for some reason, can't be read
get
{
throw new Exception("Something bad happened");
}
}
public Customer Customer
{
get { return _customer; }
}
}
Aquí está la implementación:
public static void WriteObject(string name, object target, XmlWriter writer)
{
WriteObject(name, target, writer, new List<object>(), 0, 10, -1);
}
private static void WriteObject(string name, object target, XmlWriter writer, List<object> recurringObjects, int depth, int maxDepth, int maxListLength)
{
var formatted = TryToFormatPropertyValueAsString(target);
if (formatted != null)
{
WriteSimpleProperty(name, formatted, writer);
}
else if (target is IEnumerable)
{
WriteCollectionProperty(name, (IEnumerable)target, writer, depth, maxDepth, recurringObjects, maxListLength);
}
else
{
WriteComplexObject(name, target, writer, recurringObjects, depth, maxDepth, maxListLength);
}
}
private static void WriteComplexObject(string name, object target, XmlWriter writer, List<object> recurringObjects, int depth, int maxDepth, int maxListLength)
{
if (target == null || depth >= maxDepth) return;
if (recurringObjects.Contains(target))
{
writer.WriteStartElement(name);
writer.WriteAttributeString("Ref", (recurringObjects.IndexOf(target) + 1).ToString());
writer.WriteEndElement();
return;
}
recurringObjects.Add(target);
writer.WriteStartElement(name);
writer.WriteAttributeString("Ref", (recurringObjects.IndexOf(target) + 1).ToString());
foreach (var property in target.GetType().GetProperties())
{
var propertyValue = ReadPropertyValue(target, property);
WriteObject(property.Name, propertyValue, writer, recurringObjects, depth + 1, maxDepth, maxListLength);
}
writer.WriteEndElement();
}
private static object ReadPropertyValue(object target, PropertyInfo property)
{
try { return property.GetValue(target, null); }
catch (Exception ex) { return ReadExceptionMessage(ex); }
}
private static string ReadExceptionMessage(Exception ex)
{
if (ex is TargetInvocationException && ex.InnerException != null)
return ReadExceptionMessage(ex.InnerException);
return ex.Message;
}
private static string TryToFormatPropertyValueAsString(object propertyValue)
{
var formattedPropertyValue = null as string;
if (propertyValue == null)
{
formattedPropertyValue = string.Empty;
}
else if (propertyValue is string || propertyValue is IFormattable || propertyValue.GetType().IsPrimitive)
{
formattedPropertyValue = propertyValue.ToString();
}
return formattedPropertyValue;
}
private static void WriteSimpleProperty(string name, string formattedPropertyValue, XmlWriter writer)
{
writer.WriteStartElement(name);
writer.WriteValue(formattedPropertyValue);
writer.WriteEndElement();
}
private static void WriteCollectionProperty(string name, IEnumerable collection, XmlWriter writer, int depth, int maxDepth, List<object> recurringObjects, int maxListLength)
{
writer.WriteStartElement(name);
var enumerator = null as IEnumerator;
try
{
enumerator = collection.GetEnumerator();
for (var i = 0; enumerator.MoveNext() && (i < maxListLength || maxListLength == -1); i++)
{
if (enumerator.Current == null) continue;
WriteComplexObject(enumerator.Current.GetType().Name, enumerator.Current, writer, recurringObjects, depth + 1, maxDepth, maxListLength);
}
}
catch (Exception ex)
{
writer.WriteElementString(ex.GetType().Name, ReadExceptionMessage(ex));
}
finally
{
var disposable = enumerator as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
writer.WriteEndElement();
}
}
todavía estaría interesado en saber si hay más soluciones probadas.
¡Buena pregunta! : P – Cerebrus
pregunta interesante. Me temo que los requisitos de "No debería importar sobre constructores" podrían no ser posibles. ¿Cómo puede el serializador saber qué constructor usar si mi tipo tiene 10 constructores diferentes? –
@siz - Debo aclarar. No le importa porque solo está serializando (objeto a XML) y nunca deserializando (XML a objeto). Eso es lo que quise decir con una solución de "solo escritura". –