2012-01-06 136 views
2

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

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,你应该只创建一个新的C#程序下载它,那么你可以测试这个很容易地在LinkPAD

Debug.WriteLine(item.DisplayName(i => i.FName)); 
+0

工程,当我尝试它。当你尝试时出了什么问题?另外,你已经包含了代码是很好的,但是最小的*编译*例子真的有帮助。 – AakashM 2012-01-09 13:56:43

+0

有两个部分类,一个具有许多属性,它是生成的类,然后是上面的类,它是元数据类。我只能得到生成的类的属性,而没有任何内部类。该项目非常庞大,所以我无法创建这样一个小例子。我试图将其烘焙到最基本的组件上。 – ewahner 2012-01-09 14:58:30

+0

是的,我知道这很棘手。但是鉴于'internal'和'public'之间的行为有所不同,我们确实需要知道* assembly *分界线在哪里。按照现状,唯一的* reflection *调用是'GetCustomAttributes',并且没有任何'BindingFlags'参数。 – AakashM 2012-01-09 15:31:45

回答

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财产;然后你在T.FName上寻找DisplayNameAttribute,但是没有一个,所以这就是停止的地方。

你需要做的是,在你没有找到你需要的属性的情况下,

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) 
    { 

查找t的成员

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

(I”:作为最初被谈论(BindingFlags.NonPublic再次)

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

如果有,就用你的辅助方法,但是这一次它传递的元数据类型的成员,他同一个名字VE这里省略最后的无效支票,以及关闭控制流量)

你如何难吃发现"Metadata"字符串,可以转而做一些声明与属性取决于:

  • 有去上SpGetProfileInfoResult类级别属性(一块你可以变化),使用typeof(这是the approach taken by System.ComponentModel),或
  • 有去上一个类级别的属性在其MetadataMetadata,声称“我是元数据类型”。然后,而不是搜索名为的固定字符串的嵌套类,而是搜索具有此特定属性的嵌套类。
+0

很好想出答案。大概比我的解决方案稍微优雅一点,但看起来我们似乎有同样的答案。 – ewahner 2012-01-10 13:08:52

0

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

我知道的一件事是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

+0

我更新了我的示例。你对GetAttribute是正确的。我添加了这个功能,所以整个例子都很清楚。我还更新了班级以显示该工具如何生成班级。由于元数据是内部的,因此我无法以您展示的方式访问它。 – ewahner 2012-01-09 12:02:27

1

经过这一段时间,我想出了一个黑客。我确信有人可以帮我清理一下,但这是我发现的作品。我必须将“元数据”嵌套类添加到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; 
}