简体   繁体   English

在给定支持字段的情况下获取C#auto属性的PropertyInfo

[英]Get PropertyInfo of a C# auto property given the backing field

I am implementing a custom IFormatter to serialize objects into a custom format that is required by our legacy systems. 我正在实现一个自定义IFormatter,将对象序列化为我们的遗留系统所需的自定义格式。

If I declare a C# auto property: 如果我声明一个C#auto属性:

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

And then in my custom serialize method I get the serialized fields via: 然后在我的自定义序列化方法中,我通过以下方式获取序列化字段:

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

How can I access the StringLength attribute that decorates the auto Property? 如何访问装饰auto属性的StringLength属性?

I am currently getting the property info by taking advantage of the <PropertyName>k_backingfield naming convention. 我目前通过利用<PropertyName>k_backingfield命名约定获取属性信息。 I'd rather not rely on this as it seems to be a specific detail of the C# compiler implementation. 我宁愿不依赖它,因为它似乎是C#编译器实现的具体细节。 Is there a better way? 有没有更好的办法?

The better way would be to stop relying on private fields for serialization (as FormatterServices.GetSerializableMembers returns) and only use public Properties instead. 更好的方法是停止依赖私有字段进行序列化(如FormatterServices.GetSerializableMembers返回),而只使用公共属性。

It is a LOT cleaner and works in this specific case. 它是一个很多清洁工,在这种特殊情况下工作。

But due to legacy code you might want to continue to use FormatterServices.GetSerializableMembers and in this case, no there is no other options for you other than using the naming convention (Or a little bit of IL analysis) and it may break at each new compiler release. 但由于遗留代码,您可能希望继续使用FormatterServices.GetSerializableMembers ,在这种情况下,除了使用命名约定(或者一点IL分析)之外,没有其他选项,并且它可能会在每个新的编译器发布。

Just for fun here is some code to do a little bit of IL analysis (It lack ignoring NOOPs and other niceties but should work with most current compilers. If you really adopt such a solution check the Cecil library (written by Jb Evain ) as it contains a full decompiler and it's better than doing it by hand. 只是为了好玩,这里有一些代码可以做一些IL分析(它缺少忽略NOOP和其他细节,但应该适用于大多数当前的编译器。如果你真的采用这样的解决方案,请检查Cecil库(由Jb Evain编写),因为它包含一个完整的反编译器,它比手工完成更好。

It's usage is like this : 它的用法是这样的:

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

And the code : 和代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何从PropertyInfo获取Getter支持字段? - How to get Getter backing field from PropertyInfo? 我可以为 C# 自动实现的属性(也称为自动支持字段)定义自定义 getter 吗? - Can I define a custom getter for a C# auto-implemented property (a.k.a. auto backing field)? 在自动属性中访问支持字段 - Acessing the backing field in an auto property 自动实现的get中的C#支持字段背后的代码是什么? 组;? - What's the code behind a C# backing field in a auto implemented get; set;? C#6自动初始化属性和支持字段的使用 - C# 6 Auto Initialization Property and the use of backing fields 将代码添加到 C# 获取/设置属性而不需要支持字段? - Add code to C# get/set of property without needing backing field? 正确编码直接访问Property C的支持字段# - Proper Coding Direct Access to the backing field of a Property C# C# - 自动属性和返回支持字段之间的区别? - C# - Difference between Automatic Property and returning a backing field? C#中公共属性支持私有字段的XML序列化 - XML Serialization of public property backing private field in C# 引用自动实现属性的支持字段 - Reference to the backing field of auto-implemented property
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM