Ho finalmente concluso la riscrittura completa di una generica implementazione di IXmlSerializable.
Ovviamente per essere generica usa reflection e non ho fatto l'ottimizzazione dell'implementazione di default, cioè la generazione al volo con CodeDom del codice per serializzare/deserializzare, in quanto per i progetti in cui mi serve la performance di questo codice non è, al momento, critica. In più ci sono già voluti alcuni giorni per considerare tutti i casi imposti dagli attributi XmlElement, XmlChoice, XmlArray, etc. etc.
Perché rifarla da zero? Perché abbiamo bisogno di due cose:
- Avere un evento nella entity prima e dopo la serializzazione (cosa non banale quando la entity è in mezzo ad un mare di oggetti dentro un domain model).
- Poter modificare liberamente il risultato della serializzazione senza dover riscrivere ad-hoc il codice per ogni singola entity (lavoro massacrante).
Purtroppo riscrivere un XmlWriter non aiuta in modo granulare e così via alla riscrittura. Adesso ogni entity riceve, se vuole, un evento con un XDocument (superbo modo di gestire XML) che può modificare come più gli piace dopo la serializzazione e prima della deserializzazione.
Durante le varie manipolazioni con Reflection ho incontrato una piccola stranezza. Quando chiamo GetProperties l'array delle proprietà viene restituito in modo comodo a chi l'ha implementato, ma anche in modo "illogico".
La lista infatti è in ordine di dichiarazione della classe ma nella gerarchia sono elencate le proprietà a partire dalla classe in questione a scendere verso quella base.
Il serializzatore XML di default invece (a meno di non specificare un Order preciso) serializza dalla base risalendo la gerarchia, cosa che appare logica visto che una classe derivata "specializza" e quindi di fatto aggiunge delle proprietà alle classi di base.
Da qui il listato che segue, probabilmente ancora ottimizzabile ma perfetto per il mio scopo. Ovviamente fare il reverse delle proprietà non andrebbe bene perché il loro ordine è corretto per ogni livello della gerarchia di derivazione.
1: /// <summary>
2: /// Same as Type.GetProperties, but returning properties in the correct order
3: /// The problem is that Type.GetProperties returns derived properties first,
4: /// while the XML Serializer want the base properties first.
5: /// </summary>
6: /// <param name="t"></param>
7: /// <returns></returns>
8: private PropertyInfo[] GetProperties(Type t)
9: {
10: //PropertyInfo[] properties = t.GetProperties(
11: // BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
12:
13: List<PropertyInfo> props = new List<PropertyInfo>();
14: List<Type> types = new List<Type>();
15: // Take the list of all base types
16: do
17: {
18: types.Add(t);
19: t = t.BaseType;
20: }
21: while(t != typeof(object));
22: // invert the type list.
23: // After this the list start from the base types to the derived ones
24: types.Reverse();
25:
26: foreach(Type x in types)
27: {
28: // for each type in the list, I take only the public properties
29: // It's foundamental to get only the properties declared in that level
30: // by using "DeclaredOnly" binding flag.
31: PropertyInfo[] properties = x.GetProperties(
32: BindingFlags.GetProperty | BindingFlags.Instance |
33: BindingFlags.Public | BindingFlags.DeclaredOnly);
34: props.AddRange(properties);
35: }
36: // finally I can return the PropertyInfo array
37: return props.ToArray<PropertyInfo>();
38: }
Sto anche pensando di pubblicare la serializzazione XML su Codeplex, ma non so se sarebbe di interesse...