Reflection - รับชื่อคุณลักษณะและค่าของคุณสมบัติ


253

ฉันมีชั้นเรียนให้เรียกมันว่าจองด้วยคุณสมบัติที่เรียกว่าชื่อ ด้วยคุณสมบัตินั้นฉันมีแอตทริบิวต์ที่เกี่ยวข้อง

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

ในวิธีการหลักของฉันฉันใช้การไตร่ตรองและต้องการรับคู่ค่าคีย์ของแต่ละแอตทริบิวต์สำหรับแต่ละคุณสมบัติ ดังนั้นในตัวอย่างนี้ฉันคาดว่าจะเห็น "ผู้เขียน" สำหรับชื่อแอตทริบิวต์และ "ชื่อผู้ใช้" สำหรับค่าแอตทริบิวต์

คำถาม: ฉันจะรับชื่อคุณลักษณะและค่าของคุณสมบัติของฉันโดยใช้ Reflection ได้อย่างไร


อะไรที่เกิดขึ้นเมื่อคุณกำลังพยายามที่จะเข้าถึงคุณสมบัติของวัตถุที่ผ่านการสะท้อนที่คุณติดอยู่ที่ไหนสักแห่งหรือคุณต้องการสำหรับการสะท้อน
Kobe

คำตอบ:


307

ใช้typeof(Book).GetProperties()เพื่อรับอาร์เรย์ของPropertyInfoอินสแตนซ์ จากนั้นใช้GetCustomAttributes()ในแต่ละPropertyInfoเพื่อดูว่าใด ๆ ของพวกเขามีAuthorแอตทริบิวต์ หากเป็นเช่นนั้นคุณสามารถรับชื่อของคุณสมบัติจากข้อมูลคุณสมบัติและค่าคุณสมบัติจากแอตทริบิวต์

บางอย่างในบรรทัดเหล่านี้เพื่อสแกนประเภทสำหรับคุณสมบัติที่มีประเภทแอตทริบิวต์เฉพาะและเพื่อส่งคืนข้อมูลในพจนานุกรม (โปรดทราบว่าสิ่งนี้สามารถทำให้เกิดไดนามิกมากขึ้นโดยส่งผ่านประเภทไปที่รูทีน):

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

16
ฉันหวังว่าฉันจะได้ไม่ต้องทิ้งคุณสมบัติ
developerdoug

prop.GetCustomAttributes (true) จะส่งคืนวัตถุเท่านั้น [] หากคุณไม่ต้องการแคสต์คุณสามารถใช้การสะท้อนกับอินสแตนซ์ของแอตทริบิวต์ได้
Adam Markowitz

AuthorAttribute ที่นี่คืออะไร? เป็นคลาสที่มาจากแอตทริบิวต์หรือไม่
@Adam

1
ใช่. OP กำลังใช้แอตทริบิวต์ที่กำหนดเองชื่อ 'ผู้แต่ง' ดูตัวอย่างได้ที่นี่: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz

1
ค่าใช้จ่ายประสิทธิภาพการหล่อแอตทริบิวต์นั้นไม่มีนัยสำคัญมากเมื่อเปรียบเทียบกับการดำเนินการอื่น ๆ ที่เกี่ยวข้อง (ยกเว้นการตรวจสอบค่า null และการกำหนดสตริง)
SilentSin

112

เพื่อรับคุณสมบัติทั้งหมดของคุณสมบัติในพจนานุกรมใช้สิ่งนี้:

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

อย่าลืมเปลี่ยนจากfalseเป็นtrueหากคุณต้องการรวมแอตทริบิวต์ที่สืบทอดมาด้วย


3
สิ่งนี้ทำได้อย่างมีประสิทธิภาพเช่นเดียวกับทางออกของอดัม แต่มีความรัดกุมมากกว่า
Daniel Moore

31
ผนวก .OfType <AuthorAttribue> () เข้ากับนิพจน์แทน ToDictionary หากคุณต้องการคุณลักษณะของผู้แต่งและต้องการข้ามนักแสดงในอนาคต
Adrian Zanescu

2
สิ่งนี้จะไม่เกิดข้อยกเว้นเมื่อมีคุณลักษณะสองประเภทที่เหมือนกันในคุณสมบัติเดียวกันหรือไม่
Konstantin

53

หากคุณต้องการค่าแอททริบิวที่ระบุหนึ่งค่าตัวอย่างเช่นแอททริบิวการแสดงผลคุณสามารถใช้รหัสต่อไปนี้:

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

30

ฉันได้แก้ไขปัญหาที่คล้ายกันโดยการเขียนตัวช่วยคุณสมบัติคุณสมบัติส่วนขยายทั่วไป:

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

การใช้งาน:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"

1
ฉันจะรับ attribute description จาก const ได้Fieldsอย่างไร
อาเมียร์

1
คุณจะได้รับ: ข้อผิดพลาด 1775 สมาชิก 'Namespace.FieldName' ไม่สามารถเข้าถึงได้ด้วยการอ้างอิงอินสแตนซ์ ผ่านการรับรองด้วยชื่อประเภทแทน หากคุณต้องการทำสิ่งนี้ฉันแนะนำให้เปลี่ยน 'const' เป็น 'อ่านได้อย่างเดียว'
Mikael Engver

1
คุณควรจะได้รับคะแนนเสียงที่เป็นประโยชน์มากกว่านั้นจริงๆ มันเป็นคำตอบที่ดีและเป็นประโยชน์กับหลาย ๆ กรณี
David Létourneau

1
ขอบคุณ @ DavidLétourneau! ใครจะหวังได้ ดูเหมือนว่าคุณช่วยนิดหน่อยในเรื่องนั้น
Mikael Engver

:) คุณคิดว่าเป็นไปได้หรือไม่ที่จะมีค่าของคุณสมบัติทั้งหมดสำหรับหนึ่งคลาสโดยใช้วิธีการทั่วไปของคุณและกำหนดค่าของคุณสมบัติให้กับคุณสมบัติแต่ละรายการ
David Létourneau

21

คุณสามารถใช้GetCustomAttributesData()และGetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);

4
ความแตกต่างคืออะไร
Prime By Design

1
@PrimeByDesign อดีตค้นหาวิธีสร้างอินสแตนซ์ของแอตทริบิวต์ที่ใช้ อันที่จริงจะยกตัวอย่างคุณลักษณะเหล่านั้น
HappyNomad

12

หากคุณหมายถึง "สำหรับแอททริบิวต์ที่รับพารามิเตอร์หนึ่งตัวให้ระบุชื่อแอตทริบิวต์และพารามิเตอร์ - ค่า" ซึ่งจะง่ายกว่าใน. NET 4.5 ผ่านCustomAttributeDataAPI:

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

3
private static Dictionary<string, string> GetAuthors()
{
    return typeof(Book).GetProperties()
        .SelectMany(prop => prop.GetCustomAttributes())
        .OfType<AuthorAttribute>()
        .ToDictionary(attribute => attribute.Name, attribute => attribute.Name);
}

2

ในขณะที่คำตอบที่ได้รับการโหวตสูงสุดข้างต้นใช้ได้ผลแน่นอนฉันขอแนะนำให้ใช้แนวทางที่แตกต่างกันเล็กน้อยในบางกรณี

หากคลาสของคุณมีคุณสมบัติหลายอย่างที่มีแอททริบิวต์เดียวกันเสมอและคุณต้องการดึงแอททริบิวเหล่านั้นลงในพจนานุกรมนี่คือวิธี:

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

สิ่งนี้ยังคงใช้การส่งสัญญาณ แต่ทำให้แน่ใจว่าการส่งสัญญาณจะทำงานได้ตลอดเวลาเนื่องจากคุณจะได้รับแอตทริบิวต์แบบกำหนดเองของประเภท "AuthorName" เท่านั้น หากคุณมีหลายคุณสมบัติข้างต้นคำตอบจะได้รับการยกเว้นนักแสดง


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

การใช้งาน:

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

หรือ:

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

1

นี่คือวิธีคงที่บางส่วนที่คุณสามารถใช้เพื่อรับ MaxLength หรือแอตทริบิวต์อื่น ๆ

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

}

กำลังใช้วิธีการคงที่ ...

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

หรือใช้วิธีการขยายเพิ่มเติมในอินสแตนซ์ ...

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

หรือใช้วิธีการแบบคงที่เต็มรูปแบบสำหรับคุณลักษณะอื่น ๆ (เช่น StringLength) ...

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

แรงบันดาลใจจากคำตอบของ Mikael Engver


1

Necromancing
สำหรับผู้ที่ยังต้องบำรุงรักษา. NET 2.0 หรือผู้ที่ต้องการดำเนินการโดยไม่มี LINQ:

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

ตัวอย่างการใช้งาน:

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

หรือเพียงแค่

string aname = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, a => a.Name );

0
foreach (var p in model.GetType().GetProperties())
{
   var valueOfDisplay = 
       p.GetCustomAttributesData()
        .Any(a => a.AttributeType.Name == "DisplayNameAttribute") ? 
            p.GetCustomAttribute<DisplayNameAttribute>().DisplayName : 
            p.Name;
}

ในตัวอย่างนี้ฉันใช้ DisplayName แทนผู้แต่งเนื่องจากมีฟิลด์ชื่อ 'DisplayName' ที่จะแสดงพร้อมค่า


0

เพื่อรับคุณลักษณะจาก enum ฉันใช้:

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

// การใช้

 var _message = Translate(code);

0

เพียงมองหาสถานที่ที่เหมาะสมในการใส่รหัสชิ้นนี้

สมมติว่าคุณมีคุณสมบัติดังต่อไปนี้:

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

และคุณต้องการรับค่า ShortName คุณทำได้:

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

หรือเพื่อให้เป็นเรื่องทั่วไป:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.