2011-12-02 9 views
5

Implementuję niestandardowy obiekt IFormatter do przekształcania obiektów do niestandardowego formatu, który jest wymagany przez nasze dotychczasowe systemy.Uzyskaj właściwość właściwości Auto C# z pola wsparcia

Jeśli oświadczam właściwość C# Auto:

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

a następnie w moim metoda niestandardowy serializacji dostaję szeregowej pola przez:

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

Jak mogę uzyskać dostęp do atrybutu StringLength że ozdabia auto Property?

Aktualnie otrzymuję informacje o nieruchomościach, korzystając z konwencji nazewnictwa <PropertyName>k_backingfield. Wolałbym nie polegać na tym, ponieważ wydaje się, że jest to szczególny szczegół implementacji kompilatora C#. Czy istnieje lepszy sposób?

+0

Myślę, że utknąłeś za pomocą konwencji, brakuje dynamicznej kontroli IL dla każdego gettera właściwości lub ustawiacza w celu określenia pola odniesienia. –

+1

Czy próbowałeś wywołać [GetCustomAttributes] (http://msdn.microsoft.com/en-us/library/kff8s254.aspx) na elementach tablicy członków? – dasblinkenlight

+0

@dasblinkenlight - tak, zwraca atrybuty pola kopii, a nie właściwości. Jest to tylko atrybut generowania kodu, który jest automatycznie dodawany przez kompilację. – PhilB

Odpowiedz

4

Lepszym sposobem na przestanie polegać na prywatnych polach do serializacji (jak zwraca FormatterServices.GetSerializableMembers) i używać tylko właściwości publicznych.

Jest to środek czyszczący i działa w tym konkretnym przypadku.

Ale ze względu na dotychczasowy kod możesz nadal używać FormatterServices.GetSerializableMembers iw tym przypadku nie ma innych opcji poza konwencją nazewnictwa (lub trochę analizy IL), która może zostać złamana o każde nowe wydanie kompilatora.

Dla zabawy jest tutaj trochę kodu do wykonania analizy IL (brak ignorowania NOOP i innych nicetek, ale powinien działać z większością obecnych kompilatorów.) Jeśli tak naprawdę przyjmiesz takie rozwiązanie, sprawdź bibliotekę Cecil (napisaną przez Jb Evain .), ponieważ zawiera pełną decompiler i lepiej niż robi to ręcznie

to użycie jest tak:

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

i kod:

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

Istnieje kilka właściwości publicznych, których nie chcę serializować. Zdaję sobie sprawę, że mogę użyć adnotacji DataMember tylko do serializacji członków, którzy jawnie się zgłoszą. To może być najlepsze rozwiązanie, ale zamierzam trochę dłużej wytrzymać, zanim zaznaczę to jako odpowiedź, aby zobaczyć, czy dostaję coś, co lubię lepiej: p – PhilB

+0

@PhilB Dodano trochę zabawy kod analizy IL zasługiwać co najmniej a +10 points ^^ (Żartuję, ręczna analiza IL binarna jest fajna, nigdy tego nie robiłem, ale fajnie) –

+0

Zaakceptowana odpowiedź za korzystanie z właściwości publicznych. +1 dla pewnej zabawnej analizy IL. Mam zamiar przekazać kod komuś innemu w ciągu kilku miesięcy, więc prawdopodobnie spróbuję zachować prostotę, ale czytanie analizy IL było całkiem zabawne. – PhilB

Powiązane problemy