从内部类获取 DisplayNameAttribute

发布于 2024-12-25 06:06:38 字数 3141 浏览 4 评论 0原文

我有一个被声明为内部的类。它用各种注释进行装饰。特别是 [DisplayName("My Display Name")] 注释。我有一些代码可以检索该值,但仅在该类被声明为公共时才有效。我对使用反射有点陌生。我相信我需要指定使用 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

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

I have a class that is declared Internal. It is decorated with various annotations. In particular is the [DisplayName("My Display Name")] annotation. I have some code that will retrieve the value but only works if the class is declared public. I am sort of new to using reflection. I believe I need to specify that the BindingFlags.NonPublic be used but I am not sure where.

LinqPAD code:

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

Usage:

If you don't have LinqPAD, you should download it then you can test this pretty easily by just creating a new C# Program in LinkPAD

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

二智少女猫性小仙女 2025-01-01 06:06:38

因此,您似乎希望能够通过在单独的部分片段中提供元数据来装饰部分类的现有成员。没有内置机制(参见例如 这个问题和类答案中提到),但如果您愿意遵守约定,您可以推出自己的约定:

因此,假设我们有

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< /code>

    // 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(您可以更改的部分)上,该属性使用 指向其元数据类型 (这是 System. ComponentModel),或者
  • 有一个位于 Metadata 上的类级属性,让它声明“我是元数据类型”。然后,我们不再搜索名为固定字符串的嵌套类,而是搜索具有此特定属性的嵌套类。

So it looks like you want to be able to decorate existing members of a partial class, by providing metadata in a separate partial piece. There's no built-in mechanism for that (see eg this question and the classes mentioned in the answer), but if you're willing to stick to a convention, you can roll your own:

So suppose we have

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

in a partial piece we can't change, and

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

in a partial piece we can change. You already have most of the pieces: in DisplayName(), you successfully determine that we are looking at the FName property; you then look for a DisplayNameAttribute on T.FName, but there isn't one, so that's where it stops.

What you need to do is, in the case where you don't find the attribute you need,

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

Look for a nested class named Metadata - note here is one place we use BindingFlags.NonPublic

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

If we find one:

    if (metadataType != null)
    {

Look for a member of the same name as was originally being talked about (BindingFlags.NonPublic again)

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

If there is one, use your helper method, but this time pass it the metadata type's member:

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

(I've omitted a final nullity check here, as well as closing the control flow)

Depending on how distasteful you find that "Metadata" string, you could instead do something declarative with attributes:

  • have a class-level attribute that goes on SpGetProfileInfoResult (the piece you can change) that points at its Metadata using typeof (this is the approach taken by System.ComponentModel), or
  • have a class-level attribute that goes on Metadata, to have it claim 'I am a metadata type'. Then instead of searching for a nested class named a fixed string, we would instead search for a nested class having this particular attribute.
仙女 2025-01-01 06:06:38

经过一段时间的研究后,我想出了一个 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;
}

After working on this for a while I came up with a Hack. I am sure someone out there can help me clean this up a bit, but this is what I found works. I had to add the "Metadata" nested class to the DeclaringType and then do a GetMember on that result, which returns a collection of members.

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;
}
书信已泛黄 2025-01-01 06:06:38

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

我确实知道的一件事是 MemberInfo 没有 GetAttribute() 函数。您必须在那里使用扩展方法。

不过我可以告诉您,您不需要任何特殊的绑定标志,只是因为类型是内部。只有成员的可见性很重要,在本例中它是公开的。

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

输出:

显示名称属性

I won't try to debug your code because you're using some classes that I'm not familiar with.

One thing I do know is that MemberInfo does not have a GetAttribute() function. You must be using an extension method there.

However I can tell you that you don't need any special bindingflags just because the type is internal. Only the visibility of the member is important, and in this case it's public.

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

Output:

DisplayNameAttribute

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文