Получение атрибута DisplayNameAttribute из внутреннего класса - PullRequest
2 голосов
/ 07 января 2012

У меня есть класс, который объявлен как Internal. Он украшен различными аннотациями. В частности, это аннотация [DisplayName («Мое отображаемое имя»)]. У меня есть некоторый код, который будет получать значение, но работает, только если класс объявлен как открытый. Я новичок в использовании отражения. Я считаю, что мне нужно указать, что будет использоваться 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, вы должны загрузить его, тогда вы можете довольно легко проверить это, просто создав новую программу на C # в LinkPAD

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

Ответы [ 3 ]

3 голосов
/ 09 января 2012

Похоже, вы хотите иметь возможность декорировать существующие члены частичного класса, предоставляя метаданные в виде отдельной частичной части. Для этого нет встроенного механизма (см., Например, этот вопрос и классы, упомянутые в ответе ), но если вы хотите придерживаться соглашения, вы можете выполнить свой собственный:

Итак, предположим, у нас есть

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 (это подход, принятый System.ComponentModel) или
  • имеет атрибут уровня класса, который продолжается Metadata, чтобы он утверждал: «Я - тип метаданных». Тогда вместо поиска вложенного класса с именем фиксированной строки мы вместо этого ищем вложенный класс, имеющий этот конкретный атрибут.
1 голос
/ 10 января 2012

Поработав некоторое время, я придумал Хак.Я уверен, что кто-то может помочь мне немного это исправить, но это то, что я нашел работ.Мне пришлось добавить вложенный класс «Метаданные» в 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;
}
0 голосов
/ 07 января 2012

Я не буду пытаться отлаживать ваш код, потому что вы используете классы, с которыми я не знаком.

Я знаю одно: 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...