反射 - 获取属性名称和值的属性

345

我有一个类,让我们称之为“Book”,它具有一个名为“Name”的属性。对于该属性,我有一个相关联的属性。

public class Book
{
    [Author("AuthorName")]
    public string Name
    {
        get; private set; 
    }
}
在我的主方法中,我正在使用反射,并希望为每个属性获取每个属性的键值对。因此,在这个例子中,我期望看到属性名为“Author”,属性值为“AuthorName”。
问题:如何使用反射获取我的属性上的属性名称和值?

当您尝试通过反射访问对象上的属性时,会发生什么情况?您是被卡住了还是需要反射代码? - kobe
16个回答

401
使用 typeof(Book).GetProperties() 获取一个 PropertyInfo 实例数组。然后对每个 PropertyInfo 使用 GetCustomAttributes() 方法,检查它们是否具有 Author 属性类型。如果有的话,可以从属性信息中获取属性名称以及从属性中获取属性值。
以下示例可用于扫描具有特定属性类型的类型的属性,并在字典中返回数据(请注意,通过将类型传递给该方法,此方法可以更加动态):
public static Dictionary<string, string> GetAuthors()
{
    Dictionary<string, string> _dict = new Dictionary<string, string>();

    PropertyInfo[] props = typeof(Book).GetProperties();
    foreach (PropertyInfo prop in props)
    {
        object[] attrs = prop.GetCustomAttributes(true);
        foreach (object attr in attrs)
        {
            AuthorAttribute authAttr = attr as AuthorAttribute;
            if (authAttr != null)
            {
                string propName = prop.Name;
                string auth = authAttr.Name;

                _dict.Add(propName, auth);
            }
        }
    }

    return _dict;
}

21
我希望我不必强制转换属性。 - user619891
prop.GetCustomAttributes(true) 只返回一个 object[]。如果您不想进行强制转换,那么可以在属性实例本身上使用反射。 - Adam Markowitz
这里的AuthorAttribute是什么?它是从Attribute派生的类吗?@Adam Markowitz - Sarath Subramanian
1
是的。OP正在使用名为“Author”的自定义属性。请参见此处的示例:http://msdn.microsoft.com/en-us/library/sw480ze8.aspx - Adam Markowitz
1
与其他操作相比(除了空值检查和字符串赋值),将属性转换的性能成本微不足道。 - SilentSin

147

要在字典中获取属性的所有属性,请使用以下方法:

typeof(Book)
  .GetProperty("Name")
  .GetCustomAttributes(false) 
  .ToDictionary(a => a.GetType().Name, a => a);

如果您想包括继承的属性,请记得将false更改为true


4
这个方法实际上与Adam的解决方案效果相同,但更加简洁。 - Daniel Moore
38
如果您只需要作者属性并想跳过未来的类型转换,请将".OfType<AuthorAttribute>()"添加到表达式中,而不是使用ToDictionary。 - Adrian Zanescu
3
当同一属性上存在两个相同类型的属性时,这会抛出异常吗? - Konstantin

98
如果你只想要一个特定的属性值,比如说 Display 属性,你可以使用 GetCustomAttribute 扩展方法 的通用版本:
var pInfo = typeof(Book).GetProperty("Name")
                             .GetCustomAttribute<DisplayAttribute>();
var name = pInfo.Name;

41

我通过编写通用的扩展属性属性辅助程序来解决类似的问题:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public static class AttributeHelper
{
    public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(
        Expression<Func<T, TOut>> propertyExpression, 
        Func<TAttribute, TValue> valueSelector) 
        where TAttribute : Attribute
    {
        var expression = (MemberExpression) propertyExpression.Body;
        var propertyInfo = (PropertyInfo) expression.Member;
        var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() as TAttribute;
        return attr != null ? valueSelector(attr) : default(TValue);
    }
}

使用方法:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"

1
我怎样可以从常量“Fields”中获取描述属性? - Amir
1
您将会得到一个错误代码:1775。成员“Namespace.FieldName”无法通过实例引用访问;请改为使用类型名称限定。如果您需要这样做,我建议将“const”更改为“readonly”。 - Mikael Engver
1
你应该有比那更多有用的投票,说实话。这是一个非常好的和有用的答案适用于许多情况。 - David Létourneau
1
感谢@DavidLétourneau!只能希望如此。看起来你在这方面有所帮助。 - Mikael Engver
你认为通过使用通用方法,可以获取一个类的所有属性值,并将属性值分配给每个属性吗? - David Létourneau
@DavidLétourneau:我认为你可以这样做,但如果你在现代环境中并且可以使用C# 6.0,我建议你使用自动属性初始化器。在这里可以看到一个例子:https://dotnetfiddle.net/N58x7J。 - Mikael Engver

22

5
有什么不同? - K-Dawg
2
@PrimeByDesign 前者找出如何实例化应用属性。后者实际上实例化这些属性。 - HappyNomad

13

如果您的意思是“对于需要一个参数的属性,列出属性名称和参数值”,那么在.NET 4.5中使用CustomAttributeData API更容易实现:

using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

public static class Program
{
    static void Main()
    {
        PropertyInfo prop = typeof(Foo).GetProperty("Bar");
        var vals = GetPropertyAttributes(prop);
        // has: DisplayName = "abc", Browsable = false
    }
    public static Dictionary<string, object> GetPropertyAttributes(PropertyInfo property)
    {
        Dictionary<string, object> attribs = new Dictionary<string, object>();
        // look for attributes that takes one constructor argument
        foreach (CustomAttributeData attribData in property.GetCustomAttributesData()) 
        {

            if(attribData.ConstructorArguments.Count == 1)
            {
                string typeName = attribData.Constructor.DeclaringType.Name;
                if (typeName.EndsWith("Attribute")) typeName = typeName.Substring(0, typeName.Length - 9);
                attribs[typeName] = attribData.ConstructorArguments[0].Value;
            }

        }
        return attribs;
    }
}

class Foo
{
    [DisplayName("abc")]
    [Browsable(false)]
    public string Bar { get; set; }
}

11
private static Dictionary<string, string> GetAuthors()
{
    return typeof(Book).GetProperties()
        .SelectMany(prop => prop.GetCustomAttributes())
        .OfType<AuthorAttribute>()
        .ToDictionary(a => a.GetType().Name.Replace("Attribute", ""), a => a.Name);
}

使用泛型的示例(目标框架4.5)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

private static Dictionary<string, string> GetAttribute<TAttribute, TType>(
    Func<TAttribute, string> valueFunc)
    where TAttribute : Attribute
{
    return typeof(TType).GetProperties()
        .SelectMany(p => p.GetCustomAttributes())
        .OfType<TAttribute>()
        .ToDictionary(a => a.GetType().Name.Replace("Attribute", ""), valueFunc);
}

使用方法

var dictionary = GetAttribute<AuthorAttribute, Book>(a => a.Name);

3
public static class PropertyInfoExtensions
{
    public static TValue GetAttributValue<TAttribute, TValue>(this PropertyInfo prop, Func<TAttribute, TValue> value) where TAttribute : Attribute
    {
        var att = prop.GetCustomAttributes(
            typeof(TAttribute), true
            ).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return value(att);
        }
        return default(TValue);
    }
}

使用方法:

 //get class properties with attribute [AuthorAttribute]
        var props = typeof(Book).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuthorAttribute)));
            foreach (var prop in props)
            {
               string value = prop.GetAttributValue((AuthorAttribute a) => a.Name);
            }

或者:

 //get class properties with attribute [AuthorAttribute]
        var props = typeof(Book).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuthorAttribute)));
        IList<string> values = props.Select(prop => prop.GetAttributValue((AuthorAttribute a) => a.Name)).Where(attr => attr != null).ToList();

3

虽然上面得票最多的答案肯定有效,但在某些情况下,我建议使用稍微不同的方法。

如果您的类有多个属性始终具有相同的属性,并且希望将这些属性按字典排序,以下是方法:

var dict = typeof(Book).GetProperties().ToDictionary(p => p.Name, p => p.GetCustomAttributes(typeof(AuthorName), false).Select(a => (AuthorName)a).FirstOrDefault());

这仍然使用了类型转换,但确保转换始终有效,因为您只会得到类型为“AuthorName”的自定义属性。如果您有多个属性,则会出现转换异常。


2

以下是一些静态方法,您可以使用它们来获取MaxLength或任何其他属性。

using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;

public static class AttributeHelpers {

public static Int32 GetMaxLength<T>(Expression<Func<T,string>> propertyExpression) {
    return GetPropertyAttributeValue<T,string,MaxLengthAttribute,Int32>(propertyExpression,attr => attr.Length);
}

//Optional Extension method
public static Int32 GetMaxLength<T>(this T instance,Expression<Func<T,string>> propertyExpression) {
    return GetMaxLength<T>(propertyExpression);
}


//Required generic method to get any property attribute from any class
public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(Expression<Func<T,TOut>> propertyExpression,Func<TAttribute,TValue> valueSelector) where TAttribute : Attribute {
    var expression = (MemberExpression)propertyExpression.Body;
    var propertyInfo = (PropertyInfo)expression.Member;
    var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute),true).FirstOrDefault() as TAttribute;

    if (attr==null) {
        throw new MissingMemberException(typeof(T).Name+"."+propertyInfo.Name,typeof(TAttribute).Name);
    }

    return valueSelector(attr);
}

}

使用静态方法可以在不实例化对象的情况下调用类中的方法。

var length = AttributeHelpers.GetMaxLength<Player>(x => x.PlayerName);

或者在实例上使用可选的扩展方法...
var player = new Player();
var length = player.GetMaxLength(x => x.PlayerName);

或者对于任何其他属性(例如StringLength),使用完整的静态方法...

var length = AttributeHelpers.GetPropertyAttributeValue<Player,string,StringLengthAttribute,Int32>(prop => prop.PlayerName,attr => attr.MaximumLength);

受Mikael Engver答案的启发。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接