c# - 在给定支持字段的情况下获取 C# 自动属性的 PropertyInfo

标签 c# c#-3.0

我正在实现自定义 IFormatter 以将对象序列化为我们的遗留系统所需的自定义格式。

如果我声明一个 C# 自动属性:

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

然后在我的自定义序列化方法中,我通过以下方式获取序列化字段:

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

如何访问修饰自动属性的 StringLength 属性?

我目前正在利用 <PropertyName>k_backingfield 获取属性信息命名约定。我不想依赖它,因为它似乎是 C# 编译器实现的特定细节。有没有更好的办法?

最佳答案

更好的方法是停止依赖私有(private)字段进行序列化(如 FormatterServices.GetSerializableMembers 返回),而只使用公共(public)属性。

它是一种很多清洁剂,适用于这种特定情况。

但由于遗留代码,您可能希望继续使用 FormatterServices.GetSerializableMembers,在这种情况下,除了使用命名约定(或一点点IL 分析),它可能会在每个新的编译器版本中中断。

只是为了好玩,这里有一些代码可以做一些 IL 分析(它没有忽略 NOOP 和其他细节,但应该适用于大多数当前的编译器。如果你真的采用这样的解决方案,请检查 Cecil 库(由Jb Evain ),因为它包含一个完整的反编译器,比手动编译要好。

它的用法是这样的:

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

和代码:

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

关于c# - 在给定支持字段的情况下获取 C# 自动属性的 PropertyInfo,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8361795/

相关文章:

c# - 如何在 C# 3.0 中的字符串数组中搜索特定字符串

c# - Linq to Entities 和串联属性

c# - 如何正确定义类属性?

jquery - 如何使用MVC3 Controller 显示 "Message box"

c# - 编码我的字符串以通过 C# 发送 http 请求

c# - 如何查找重复字符

c# - C#/软件中的端口转发可能吗?不是只能由路由器管理吗?

c# - 是否应该在每个使用 MVC 的 View 中使用 ViewModel?

c# - 类库中的 .net Reference Web

LinQ与自定义比较器不同,留下重复项