2011-12-02 14 views
5

Estoy implementando un IFormatter personalizado para serializar objetos en un formato personalizado que es requerido por nuestros sistemas heredados.Obtiene PropertyInfo de una propiedad automática de C# dado el campo de respaldo

Si declaro un inmueble C# auto:

[StringLength(15)] 
public MyProperty { get; set; } 

Y luego en mi método de serialización personalizada consigo los campos con número de serie a través de:

MemberInfo[] members = 
    FormatterServices.GetSerializableMembers(graph.GetType(), Context); 

¿Cómo puedo acceder a los atributos StringLength que decora la propiedad de auto?

Actualmente estoy obteniendo la información de la propiedad aprovechando la convención de nomenclatura <PropertyName>k_backingfield. Prefiero no confiar en esto, ya que parece ser un detalle específico de la implementación del compilador de C#. ¿Hay una mejor manera?

+0

Creo que estás atascado usando la convención, a excepción de inspeccionar dinámicamente el IL para cada getter o setter de propiedades para determinar el campo al que se hace referencia. –

+1

¿Intentó llamar a [GetCustomAttributes] (http://msdn.microsoft.com/en-us/library/kff8s254.aspx) en los elementos de la matriz de miembros? – dasblinkenlight

+0

@dasblinkenlight: sí, devuelve los atributos del campo de respaldo en lugar de la propiedad. El es solo un atributo de generación de código que se agrega automáticamente por la compilación. – PhilB

Respuesta

4

La mejor manera sería dejar de depender de los campos privados para la serialización (como FormatterServices.GetSerializableMembers devoluciones) y solo utilizar las propiedades públicas.

Es un limpiador LOTE y funciona en este caso específico.

Pero debido al código heredado puede que desee continuar usando FormatterServices.GetSerializableMembers y en este caso, no hay otras opciones para usted además de usar la convención de nomenclatura (o un poco de análisis IL) y puede romperse en cada nueva versión del compilador.

Sólo por diversión aquí es un poco de código para hacer un poco de análisis IL (TI carecen de NOOPs haciendo caso omiso y otros detalles, pero debería funcionar con la mayoría de los compiladores actuales. Si realmente adopta una solución de este tipo comprobar la biblioteca Cecil (escrito por Jb Evain .), ya que contiene un descompilador completa y es mejor que hacerlo a mano

su uso es como esto:

void Main() 
{ 
    var members = FormatterServices.GetSerializableMembers(typeof(Foo)); 
    var propertyFieldAssoc = new PropertyFieldAssociation(typeof(Foo)); 

    foreach(var member in members) 
    { 
     var attributes = member.GetCustomAttributes(false).ToList(); 
     if (member is FieldInfo) 
     { 
      var property = propertyFieldAssoc.GetProperty((FieldInfo)member); 
      if (property != null) 
      { 
       attributes.AddRange(property.GetCustomAttributes(false)); 
      } 
     } 

     Console.WriteLine(member.Name); 
     foreach(var attribute in attributes) 
     { 
      Console.WriteLine(" * {0}", attribute.GetType().FullName); 
     } 
     Console.WriteLine(); 
    } 
} 

y el código:

class PropertyFieldAssociation 
{ 
    const byte LDARG_0 = 0x2; 
    const byte LDARG_1 = 0x3; 
    const byte STFLD = 0x7D; 
    const byte LDFLD = 0x7B; 
    const byte RET = 0x2A; 

    static FieldInfo GetFieldFromGetMethod(MethodInfo getMethod) 
    { 
     if (getMethod == null) throw new ArgumentNullException("getMethod"); 

     var body = getMethod.GetMethodBody(); 
     if (body.LocalVariables.Count > 0) return null; 
     var il = body.GetILAsByteArray(); 
     if (il.Length != 7) return null; 

     var ilStream = new BinaryReader(new MemoryStream(il)); 

     if (ilStream.ReadByte() != LDARG_0) return null; 
     if (ilStream.ReadByte() != LDFLD) return null; 
     var fieldToken = ilStream.ReadInt32(); 
     var field = getMethod.Module.ResolveField(fieldToken); 
     if (ilStream.ReadByte() != RET) return null; 

     return field; 
    } 

    static FieldInfo GetFieldFromSetMethod(MethodInfo setMethod) 
    { 
     if (setMethod == null) throw new ArgumentNullException("setMethod"); 

     var body = setMethod.GetMethodBody(); 
     if (body.LocalVariables.Count > 0) return null; 
     var il = body.GetILAsByteArray(); 
     if (il.Length != 8) return null; 

     var ilStream = new BinaryReader(new MemoryStream(il)); 

     if (ilStream.ReadByte() != LDARG_0) return null; 
     if (ilStream.ReadByte() != LDARG_1) return null; 
     if (ilStream.ReadByte() != STFLD) return null; 
     var fieldToken = ilStream.ReadInt32(); 
     var field = setMethod.Module.ResolveField(fieldToken); 
     if (ilStream.ReadByte() != RET) return null; 

     return field; 
    } 

    public static FieldInfo GetFieldFromProperty(PropertyInfo property) 
    { 
     if (property == null) throw new ArgumentNullException("property"); 

     var get = GetFieldFromGetMethod(property.GetGetMethod()); 
     var set = GetFieldFromSetMethod(property.GetSetMethod()); 

     if (get == set) return get; 
     else return null; 
    } 

    Dictionary<PropertyInfo, FieldInfo> propertyToField = new Dictionary<PropertyInfo, FieldInfo>(); 
    Dictionary<FieldInfo, PropertyInfo> fieldToProperty = new Dictionary<FieldInfo, PropertyInfo>(); 

    public PropertyInfo GetProperty(FieldInfo field) 
    { 
     PropertyInfo result; 
     fieldToProperty.TryGetValue(field, out result); 
     return result; 
    } 

    public FieldInfo GetField(PropertyInfo property) 
    { 
     FieldInfo result; 
     propertyToField.TryGetValue(property, out result); 
     return result; 
    } 

    public PropertyFieldAssociation(Type t) 
    { 
     if (t == null) throw new ArgumentNullException("t"); 

     foreach(var property in t.GetProperties()) 
     { 
      Add(property); 
     } 
    } 

    void Add(PropertyInfo property) 
    { 
     if (property == null) throw new ArgumentNullException("property"); 

     var field = GetFieldFromProperty(property); 
     if (field == null) return; 
     propertyToField.Add(property, field); 
     fieldToProperty.Add(field, property); 
    } 
} 

class StringLengthAttribute : Attribute 
{ 
    public StringLengthAttribute(int l) 
    { 
    } 
} 

[Serializable] 
class Foo 
{ 
    [StringLength(15)] 
    public string MyProperty { get; set; } 

    string myField; 
    [StringLength(20)] 
    public string OtherProperty { get { return myField; } set { myField = value; } } 
} 
+0

Hay algunas propiedades públicas que no quiero serializar. Me doy cuenta de que puedo usar la anotación DataMember para serializar solo al miembro que explícitamente opt-in. Esta podría ser la mejor solución, pero voy a esperar un poco más antes de marcar esto como la respuesta para ver si obtengo algo que me gusta mejor: p – PhilB

+0

@PhilB Agregué un código de análisis IL interesante para que merezca al menos un +10 puntos ^^ (estoy bromeando, el análisis de IL binario manual es divertido, nunca lo hice antes, pero es divertido) –

+0

Respuesta aceptada para el uso de propiedades públicas. +1 para un análisis IL divertido. Voy a entregar el código a otra persona en unos meses, así que probablemente intentaré mantenerlo simple, pero leer el análisis de IL fue muy divertido. – PhilB

Cuestiones relacionadas