Reflexion - Attributname und Wert für die Eigenschaft abrufen

253

Ich habe eine Klasse, nennen wir sie Buch mit einer Eigenschaft namens Name. Mit dieser Eigenschaft ist ein Attribut verknüpft.

public class Book
{
    [Author("AuthorName")]
    public string Name
    {
        get; private set; 
    }
}

In meiner Hauptmethode verwende ich Reflektion und möchte das Schlüsselwertpaar jedes Attributs für jede Eigenschaft erhalten. In diesem Beispiel würde ich erwarten, dass "Autor" als Attributname und "Autorenname" als Attributwert angezeigt werden.

Frage: Wie erhalte ich mithilfe von Reflection den Attributnamen und den Wert für meine Eigenschaften?

Developerdoug
quelle
Was passiert, wenn Sie versuchen, durch Reflexion auf Eigenschaften auf diesem Objekt zuzugreifen, irgendwo hängen bleiben oder Code für Reflexion wollen
Kobe

Antworten:

307

Verwenden Sie typeof(Book).GetProperties()diese Option , um ein Array von PropertyInfoInstanzen abzurufen. Verwenden Sie dann GetCustomAttributes()jeweils, PropertyInfoum festzustellen, ob einer von ihnen den AuthorAttributtyp hat. In diesem Fall können Sie den Namen der Eigenschaft aus den Eigenschaftsinformationen und die Attributwerte aus dem Attribut abrufen.

Etwas in dieser Richtung, um einen Typ nach Eigenschaften zu durchsuchen, die einen bestimmten Attributtyp haben, und um Daten in einem Wörterbuch zurückzugeben (beachten Sie, dass dies durch Übergeben von Typen an die Routine dynamischer gestaltet werden kann):

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;
}
Adam Markowitz
quelle
16
Ich hatte gehofft, dass ich das Attribut nicht umsetzen muss.
Developerdoug
prop.GetCustomAttributes (true) gibt nur ein Objekt [] zurück. Wenn Sie nicht umwandeln möchten, können Sie die Attributinstanzen selbst reflektieren.
Adam Markowitz
Was ist AuthorAttribute hier? Ist es eine Klasse, die von Attribute abgeleitet ist? @ Adam Markowitz
Sarath Avanavu
1
Ja. Das OP verwendet ein benutzerdefiniertes Attribut mit dem Namen "Autor". Ein Beispiel finden Sie hier: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz
1
Die Leistungskosten für das Umwandeln des Attributs sind im Vergleich zu jeder anderen beteiligten Operation (mit Ausnahme der Nullprüfung und der Zuweisung von Zeichenfolgen) völlig unbedeutend.
SilentSin
112

Verwenden Sie Folgendes, um alle Attribute einer Eigenschaft in einem Wörterbuch abzurufen:

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

Denken Sie daran, von falsezu zu wechseln , truewenn Sie auch geerbte Attribute einschließen möchten.

Mo Valipour
quelle
3
Dies entspricht praktisch der Lösung von Adam, ist jedoch weitaus prägnanter.
Daniel Moore
31
Fügen Sie .OfType <AuthorAttribue> () anstelle von ToDictionary an den Ausdruck an, wenn Sie nur Autorenattribute benötigen und eine zukünftige Besetzung überspringen möchten
Adrian Zanescu
2
Wird dies nicht eine Ausnahme auslösen, wenn sich zwei Attribute desselben Typs in derselben Eigenschaft befinden?
Konstantin
53

Wenn Sie nur einen bestimmten Attributwert möchten, können Sie zum Beispiel Anzeigeattribut den folgenden Code verwenden:

var pInfo = typeof(Book).GetProperty("Name")
                             .GetCustomAttribute<DisplayAttribute>();
var name = pInfo.Name;
maxspan
quelle
30

Ich habe ähnliche Probleme gelöst, indem ich einen Generic Extension Property Attribute Helper geschrieben habe:

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

Verwendung:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"
Mikael Engver
quelle
1
Wie kann ich ein Beschreibungsattribut von const erhalten Fields?
Amir
1
Sie erhalten Folgendes: Fehler 1775 Auf das Mitglied 'Namespace.FieldName' kann nicht mit einer Instanzreferenz zugegriffen werden. Qualifizieren Sie es stattdessen mit einem Typnamen. Wenn Sie dies tun müssen, schlage ich vor, 'const' in 'readonly' zu ändern.
Mikael Engver
1
Sie sollten ehrlich gesagt eine viel nützlichere Stimme haben. Es ist eine sehr schöne und nützliche Antwort auf viele Fälle.
David Létourneau
1
Danke @ DavidLétourneau! Man kann nur hoffen. Scheint, als hättest du ein bisschen dabei geholfen.
Mikael Engver
:) Glauben Sie, dass es möglich ist, den Wert aller Attribute für eine Klasse mithilfe Ihrer generischen Methode zu ermitteln und den Wert des Attributs jeder Eigenschaft zuzuweisen?
David Létourneau
21

Sie können verwenden GetCustomAttributesData()und GetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);
Glassplitter
quelle
4
was ist der Unterschied?
Prime By Design
1
@PrimeByDesign Ersteres findet heraus, wie die angewendeten Attribute instanziiert werden. Letzteres instanziiert diese Attribute tatsächlich.
HappyNomad
12

Wenn Sie "für Attribute, die einen Parameter annehmen, die Attributnamen und den Parameterwert auflisten" meinen, ist dies in .NET 4.5 über die CustomAttributeDataAPI einfacher :

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; }
}
Marc Gravell
quelle
3
private static Dictionary<string, string> GetAuthors()
{
    return typeof(Book).GetProperties()
        .SelectMany(prop => prop.GetCustomAttributes())
        .OfType<AuthorAttribute>()
        .ToDictionary(attribute => attribute.Name, attribute => attribute.Name);
}
Daniel Dušek
quelle
2

Während die oben am häufigsten bewerteten Antworten definitiv funktionieren, würde ich in einigen Fällen einen etwas anderen Ansatz vorschlagen.

Wenn Ihre Klasse mehrere Eigenschaften mit immer demselben Attribut hat und Sie diese Attribute in ein Wörterbuch sortieren möchten, gehen Sie wie folgt vor:

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

Dies verwendet weiterhin Cast, stellt jedoch sicher, dass der Cast immer funktioniert, da Sie nur die benutzerdefinierten Attribute vom Typ "AuthorName" erhalten. Wenn Sie mehrere Attribute über den Antworten hätten, würde eine Besetzungsausnahme auftreten.

Mirko Brandt
quelle
1
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);
    }
}

Verwendung:

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

oder:

 //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();
Sieger
quelle
1

Hier sind einige statische Methoden, mit denen Sie die MaxLength oder ein anderes Attribut abrufen können.

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

}

Mit der statischen Methode ...

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

Oder verwenden Sie die optionale Erweiterungsmethode für eine Instanz ...

var player = new Player();
var length = player.GetMaxLength(x => x.PlayerName);

Oder verwenden Sie die vollständige statische Methode für ein anderes Attribut (z. B. StringLength) ...

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

Inspiriert von der Antwort des Mikael Engver.

Carter Medlin
quelle
1

Nekromantie.
Für diejenigen, die noch .NET 2.0 warten müssen oder die es ohne LINQ tun möchten:

public static object GetAttribute(System.Reflection.MemberInfo mi, System.Type t)
{
    object[] objs = mi.GetCustomAttributes(t, true);

    if (objs == null || objs.Length < 1)
        return null;

    return objs[0];
}



public static T GetAttribute<T>(System.Reflection.MemberInfo mi)
{
    return (T)GetAttribute(mi, typeof(T));
}


public delegate TResult GetValue_t<in T, out TResult>(T arg1);

public static TValue GetAttributValue<TAttribute, TValue>(System.Reflection.MemberInfo mi, GetValue_t<TAttribute, TValue> value) where TAttribute : System.Attribute
{
    TAttribute[] objAtts = (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), true);
    TAttribute att = (objAtts == null || objAtts.Length < 1) ? default(TAttribute) : objAtts[0];
    // TAttribute att = (TAttribute)GetAttribute(mi, typeof(TAttribute));

    if (att != null)
    {
        return value(att);
    }
    return default(TValue);
}

Anwendungsbeispiel:

System.Reflection.FieldInfo fi = t.GetField("PrintBackground");
wkHtmlOptionNameAttribute att = GetAttribute<wkHtmlOptionNameAttribute>(fi);
string name = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, delegate(wkHtmlOptionNameAttribute a){ return a.Name;});

oder einfach

string aname = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, a => a.Name );
Stefan Steiger
quelle
0
foreach (var p in model.GetType().GetProperties())
{
   var valueOfDisplay = 
       p.GetCustomAttributesData()
        .Any(a => a.AttributeType.Name == "DisplayNameAttribute") ? 
            p.GetCustomAttribute<DisplayNameAttribute>().DisplayName : 
            p.Name;
}

In diesem Beispiel habe ich DisplayName anstelle von Author verwendet, da ein Feld mit dem Namen 'DisplayName' mit einem Wert angezeigt werden soll.

petrosmm
quelle
0

Um ein Attribut von enum zu erhalten, verwende ich:

 public enum ExceptionCodes
 {
  [ExceptionCode(1000)]
  InternalError,
 }

 public static (int code, string message) Translate(ExceptionCodes code)
        {
            return code.GetType()
            .GetField(Enum.GetName(typeof(ExceptionCodes), code))
            .GetCustomAttributes(false).Where((attr) =>
            {
                return (attr is ExceptionCodeAttribute);
            }).Select(customAttr =>
            {
                var attr = (customAttr as ExceptionCodeAttribute);
                return (attr.Code, attr.FriendlyMessage);
            }).FirstOrDefault();
        }

// Verwenden

 var _message = Translate(code);
Mohamed.Abdo
quelle
0

Ich suche nur nach dem richtigen Ort, um diesen Code einzufügen.

Angenommen, Sie haben die folgende Eigenschaft:

[Display(Name = "Solar Radiation (Average)", ShortName = "SolarRadiationAvg")]
public int SolarRadiationAvgSensorId { get; set; }

Und Sie möchten den ShortName-Wert erhalten. Du kannst tun:

((DisplayAttribute)(typeof(SensorsModel).GetProperty(SolarRadiationAvgSensorId).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;

Oder um es allgemein zu machen:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
Asaf
quelle