kabucey
kabucey

Reputation: 2658

How to get a custom attribute from object instance in C#

Let's say I have a class called Test with one property called Title with a custom attribute:

public class Test
{
    [DatabaseField("title")]
    public string Title { get; set; }
}

And an extension method called DbField. I am wondering if getting a custom attribute from an object instance is even possible in c#.

Test t = new Test();
string fieldName = t.Title.DbField();
//fieldName will equal "title", the same name passed into the attribute above

Can this be done?

Upvotes: 25

Views: 61583

Answers (7)

BNG016
BNG016

Reputation: 310

Following are two extension methods that I use for such cases. these return an enumerable of Specific attributes on a type individually or on types in an assembly.

public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Type type) where TAttribute : Attribute
{
    if (type.GetCustomAttributes<TAttribute>(true).Any())
        foreach (var atribute in type.GetCustomAttributes<TAttribute>(true))
            yield return atribute;
}
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Assembly assembly) where TAttribute : Attribute
{
    return assembly.GetTypes().Where(type => type.GetCustomAttributes<TAttribute>(true).Any()).SelectMany(x => GetAttributes<TAttribute>(x)).AsEnumerable();
}

these can be enhanced further to use expressions/predicates to return the desired result.

public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Type type, Func<TAttribute, bool> predicate) where TAttribute : Attribute
{
    if (type.GetCustomAttributes<TAttribute>(true).Any())
    {
        var attributes = type.GetCustomAttributes<TAttribute>(true).Where(predicate);
        foreach (var atribute in attributes) yield return atribute;
    }
}
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Assembly assembly, Func<TAttribute, bool> predicate) where TAttribute : Attribute => assembly.GetTypes()
                                                                                                                                                                  .Where(type => type.GetCustomAttributes<TAttribute>(true).Any())
                                                                                                                                                                  .SelectMany(x => GetAttributes<TAttribute>(x))
                                                                                                                                                                  .Where(predicate)
                                                                                                                                                                  .AsEnumerable();

these above methods are generic and need the attribute type to fetch relevant attributes in a class. e.g.

    var modules = typeof(IEntity).Assembly.GetAttributes<Module>();
    var modules = typeof(MyClass).GetAttributes<Module>();

Upvotes: 0

casperOne
casperOne

Reputation: 74530

It is, but ultimately it's going to be a roundabout way, since you will get the Type instance from calling GetType on your instance that exposes the property, and then work on that(more often than not).

In this specific case, your extension method isn't going to be able to get the attribute information because all you are passing to it is a string.

Ultimately, what you need is something to get the PropertyInfo for the property from. Other answers are referring to the Type, what they lack is, this is not the only way to get the attribute information at the PropertyInfo which you want.

You can do that by passing a Type instance with a string, presumably, with the property name, so you can call GetProperty on the Type.

Another way of doing this since C# 3.0 has been to have a method that takes an Expression<T> and then use the parts of the Expression to get at the PropertyInfo. In this case, you would take an Expression<Func<string>> or something where TResult is string.

Once you have the PropertyInfo, you can call GetCustomAttributes on it, and look for your attribute.

The advantage to the expression approach is that Expression<T> derives from LambdaExpression, which you can call Compile on, and then call to get the actual value, if you need it.

Upvotes: 2

Nick Randell
Nick Randell

Reputation: 18295

Here is an approach. The extension method works, but it's not quite as easy. I create an expression and then retrieve the custom attribute.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    public class DatabaseFieldAttribute : Attribute
    {
        public string Name { get; set; }

        public DatabaseFieldAttribute(string name)
        {
            this.Name = name;
        }
    }

    public static class MyClassExtensions
    {
        public static string DbField<T>(this T obj, Expression<Func<T, string>> value)
        {
            var memberExpression = value.Body as MemberExpression;
            var attr = memberExpression.Member.GetCustomAttributes(typeof(DatabaseFieldAttribute), true);
            return ((DatabaseFieldAttribute)attr[0]).Name;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            Console.WriteLine("DbField = '{0}'", p.DbField(v => v.Title));

        }
        [DatabaseField("title")]
        public string Title { get; set; }

    }
}

Upvotes: 31

garik
garik

Reputation: 5746

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();

            Console.WriteLine(t.FieldName("Title").FieldName<DatabaseFieldAttribute>());
            Console.WriteLine(t.FieldName("Title").FieldIsPrimaryKey<DatabaseFieldAttribute>());
        }


    }

    public class Test
    {
        [DatabaseField("titlezzz", true)]
        public string Title
        {
            get;
            set;
        }
    }


    public class BaseDatabaseFieldAttribute : Attribute
    {
        private readonly string _name;

        public string Name { get { return _name; } }

        public BaseDatabaseFieldAttribute(string name)
        {
            _name = name;
        }
    }
    public class DatabaseFieldAttribute : BaseDatabaseFieldAttribute
    {
        private readonly bool _isPrimaryKey;

        public bool IsPrimaryKey { get { return _isPrimaryKey; } }

        public DatabaseFieldAttribute(string name, bool isPrimaryKey): base(name)
        {
            _isPrimaryKey = isPrimaryKey;
        }
    }

    public static class Helper
    {

        public static PropertyInfo FieldName(this object obj, string propertyName)
        {
            return obj.GetType().GetProperty(propertyName);
        }

        public static string FieldName<T>(this PropertyInfo property) where T: BaseDatabaseFieldAttribute
        {
            object[] os = property.GetCustomAttributes(typeof(T), false);

            if (os != null && os.Length >= 1)
                return (os[0] as T).Name;
            else
                return "N/A";
        }

        public static bool? FieldIsPrimaryKey<T>(this PropertyInfo property) where T : DatabaseFieldAttribute
        {
            object[] os = property.GetCustomAttributes(typeof(T), false);

            if (os != null && os.Length >= 1)
                return (os[0] as T).IsPrimaryKey;
            else
                return null;
        }
    }


}

Upvotes: 6

WayneC
WayneC

Reputation: 2560

As as been pointed out, it's not possible with the syntax the original poster described, because you can't get a reference to PropertyInfo inside the extension method. What about something like this:

// Extension method
public static string GetDbField(this object obj, string propertyName)
{
    PropertyInfo prop = obj.GetType().GetProperty(propertyName);
    object[] dbFieldAtts = prop.GetCustomAttributes(typeof(DatabaseFieldAttribute), true);

    if (dbFieldAtts != null && dbFieldAtts.Length > 0)
    {
        return ((DatabaseFieldAttribute)dbFieldAtts[0]).Name;
    }

    return "UNDEFINED";
}

You could then get the info as simply as:

Test t = new Test();
string dbField = t.GetDbField("Title");

Upvotes: 1

NerdFury
NerdFury

Reputation: 19214

In order to get the attribute value, you need the type that the attribute applies to. Your extension method is only getting a string value (the value of Title), so you would not be able to get the actual instance that the string came from, and so you can't get the original type that the Title property belongs to. This will make it impossible to get the attribute value from your extension method.

Upvotes: 0

David Morton
David Morton

Reputation: 16505

No, it's not possible. The reason for this is that it's the value, and not the property itself that would be sent into any custom extension method that would fetch this information. Once you get into that extension method, there's no reliable way to trace back to the property itself.

It might be possible for enum values, but as far as properties on POCO's, it's not going to work.

Upvotes: 0

Related Questions