从Internal类获取DisplayNameAttribute

时间:2012-01-06 22:31:10

标签: c# .net reflection

我有一个被宣布为内部的类。装饰有各种注释。特别是[DisplayName(“我的显示名称”)]注释。我有一些代码将检索值,但只有在类被声明为public时才有效。我对使用反射有点新意。我相信我需要指定使用BindingFlags.NonPublic,但我不知道在哪里。

LinqPAD代码:

void Main()
{
    List<SpGetProfileInfoResult> p = new List<SpGetProfileInfoResult>();
    p.Add(new SpGetProfileInfoResult() { FName = "Eric" });
    p.Add(new SpGetProfileInfoResult() { FName = "Mike" });

    p.Dump();

    foreach (var item in p)
    {
        Console.WriteLine(item.DisplayName(i => i.FName));
        Console.WriteLine(item.FName);
    }

}

public partial class SpGetProfileInfoResult
{
    // Uncomment this annotation to see that this part will work
    // [System.ComponentModel.DisplayNameAttribute("[BILLTO-FNAME]")]
    public string FName { get; set; }
}

public partial class SpGetProfileInfoResult
{
    internal class Metadata
    {
        // This attribute is never available seems.
        [System.ComponentModel.DisplayNameAttribute("[BILL-FNAME]")]
        public string FName { get; set; }
    }
}

public static class Tag
{
    public static T GetAttribute<T>(this MemberInfo member, bool isRequired) where T : Attribute
    {
        var attribute = member.GetCustomAttributes(typeof(T), false).SingleOrDefault();

        if (attribute == null && isRequired)
        {
            throw new ArgumentException(
                string.Format(
                "The {0} attribute must be defined on member {1}",
                typeof(T).Name,
                member.Name));
        }

        return (T)attribute;
    }

    public static string DisplayName<T>(this T src,Expression<Func<T, object>> propertyExpression)
    {
        Type metadata = null;

        var memberInfo = GetPropertyInformation(propertyExpression.Body);
        if (memberInfo == null)
        {
            throw new ArgumentException(
                "No property reference expression was found.",
                "propertyExpression");
        }

        var attr = memberInfo.GetAttribute<DisplayNameAttribute>(false);
        if (attr == null)
        {
            return memberInfo.Name;
        }

        return attr.DisplayName;
    }

    public static MemberInfo GetPropertyInformation(Expression propertyExpression)
    {
        MemberExpression memberExpr = propertyExpression as MemberExpression;
        if (memberExpr == null)
        {
            UnaryExpression unaryExpr = propertyExpression as UnaryExpression;
            if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
            {
                memberExpr = unaryExpr.Operand as MemberExpression;
            }
        }

        if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
        {
            return memberExpr.Member;
        }

        return null;
    }
}

用法:

如果你没有LinqPAD,你应该下载它,然后你可以通过在LinkPAD

中创建一个新的C#程序来轻松地测试它
Debug.WriteLine(item.DisplayName(i => i.FName));

3 个答案:

答案 0 :(得分:3)

因此,您希望能够通过在单独的部分片段中提供元数据来装饰部分类的现有成员。没有内置的机制(参见例如this question and the classes mentioned in the answer),但如果你愿意坚持一个约定,你可以自己动手:

假设我们有

public partial class SpGetProfileInfoResult
{
    public string FName { get; set; }
}

无法更改,

public partial class SpGetProfileInfoResult
{
    internal class Metadata
    {
        [System.ComponentModel.DisplayNameAttribute("[BILL-FNAME]")]
        public string FName { get; set; }
    }
}

可以更改的部分内容中。您已经拥有大部分内容:在DisplayName()中,您成功确定我们正在查看FName属性;然后你在DisplayNameAttribute上寻找T.FName,但没有一个,所以就是它停止的地方。

在您找不到所需属性的情况下,您需要做的是

var attr = memberInfo.GetAttribute<DisplayNameAttribute>(false);
if (attr == null)
{

查找名为Metadata的嵌套类 - 请注意,这里是我们使用的一个地方BindingFlags.NonPublic

    // Try and get a nested metadata class
    var metadataType = typeof(T)
        .GetNestedType("Metadata", 
                       BindingFlags.Public | BindingFlags.NonPublic);

如果我们找到一个:

    if (metadataType != null)
    {

寻找与最初被谈论的同名的成员(BindingFlags.NonPublic再次)

        var membersOnMetadataType = metadataType.GetMember(memberInfo.Name, 
            BindingFlags.Instance |
            BindingFlags.Public | 
            BindingFlags.NonPublic);

如果有,请使用您的帮助方法,但这次将元数据类型的成员传递给它:

        if (membersOnMetadataType.Any())
        {
            var attrOnMetadataType = membersOnMetadataType[0]
                .GetAttribute<DisplayNameAttribute>(false);
            return attrOnMetadataType.DisplayName;

(我在这里省略了最后的无效检查,以及关闭控制流程)

根据您发现"Metadata"字符串的不愉快程度,您可以使用属性进行声明性操作:

  • 有一个类级别属性,SpGetProfileInfoResult(您 }使用Metadata指向其typeof(这是{ {3}})或
  • 具有Metadata的类级属性,以使其声明“我是元数据类型”。然后,我们不是搜索一个名为的嵌套类一个固定字符串,而是搜索具有此特定属性的嵌套类。

答案 1 :(得分:1)

在研究了一段时间后,我想出了一个Hack。我相信那里有人可以帮我清理一下,但这就是我发现的作品。我必须将“元数据”嵌套类添加到DeclaringType中,然后对该结果执行GetMember,它返回一组成员。

public static string DisplayName<T>(this T src, Expression<Func<T, object>> propertyExpression)
{
    var memberInfo = GetPropertyInformation(propertyExpression.Body);
    var mytype = src.GetType();
    string strType = mytype.Name + "+Metadata";
    var metaType = Type.GetType(strType);
    MemberInfo[] mem = metaType.GetMember(memberInfo.Name);
    var att = mem[0].GetCustomAttributes(typeof(DisplayNameAttribute), true).FirstOrDefault() as DisplayNameAttribute;

    if (att == null)
        return memberInfo.Name;
    else
        return att.DisplayName;
}

答案 2 :(得分:0)

我不会尝试调试您的代码,因为您正在使用一些我不熟悉的类。

我知道的一件事是MemberInfo没有GetAttribute()功能。你必须在那里使用扩展方法。

但是我可以告诉你,因为类型是internal,你不需要任何特殊的绑定标志。只有成员的可见性很重要,在这种情况下它是公开的。

using System;
using System.ComponentModel;

namespace ConsoleApplication1
{
    internal class Metadata
    {
        [DisplayName("[BILL-FNAME]")]
        public string FName { get; set; }
    } 

    class Program
    {
        static void Main()
        {
            var memberInfo = typeof(Metadata).GetMember("FName")[0];
            var atrributes = memberInfo.GetCustomAttributes(false);
            Console.WriteLine(atrributes[0].GetType().Name);
        }
    }
}

输出:

  

DisplayNameAttribute