2
\$\begingroup\$

I have the following class structure:

MyAttribute.cs

public sealed class MyAttribute : Attribute
{
    public MyAttribute(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException(nameof(name));
        }

        Name = name;
    }

    public string Name { get; }
}

Matrix.cs

public class Matrix
{
    [MyAttribute("awesome_name")]
    public string AwesomeName { get; set; }
}

And we make an method to "extract" the value defined on name of MyAttribute:

AttributeExtensions.cs

public static class AttributeExtensions
{
    public static string GetMyAttributeNameOfProperty<TClass>(Expression<Func<TClass, object>> expression)
    {
        if (expression.Body is MemberExpression memberExpression)
        {
            var propertyInfo = (PropertyInfo)memberExpression.Member;

            var hasMyAttribute = propertyInfo.IsDefined(typeof(MyAttribute), false);

            if (hasMyAttribute)
            {
                return propertyInfo.GetCustomAttribute<MyAttribute>().Name;
            }
        }

        return default;
    }
}

Finally, we call this way:

AttributeExtensions.GetMyAttributeNameOfProperty<Matrix>(exp => exp.AwesomeName)

This call, returns: "awesome_name"

We have some doubts:

  1. Exists an more "clean" way to pass the property of <TClass>(exp.AwesomeName) to this extension method?

  2. We really need of Expression<>? It's possible to make this only with Func<TClass, object>?

Like:

GetMyAttributeNameOfProperty<TClass>(Func<TClass, object> expression)

Any suggestions?

\$\endgroup\$
4
  • \$\begingroup\$ Code Review requires concrete code from a project, with enough code and / or context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site. Please take a look at the help center. \$\endgroup\$ – Mast Mar 18 '20 at 7:40
  • 1
    \$\begingroup\$ @Mast this is a concrete code. How use this, has been explained on question: "Finally, we call this way: AttributeExtensions.GetMyAttributeNameOfProperty<Matrix>(exp => exp.AwesomeName)" Please, don't vote down! This is an solution and fully works! \$\endgroup\$ – Igor Mar 18 '20 at 14:46
  • 2
    \$\begingroup\$ While in the general case, editing a question after receiving an answer would be disallowed because it usually invalidates any reviews given, the changes applied here are okay, because they are not mentioned in the answer. Please keep in mind that editing questions after receiving answers is usually not okay. Thanks! \$\endgroup\$ – Vogel612 Mar 21 '20 at 18:44
  • \$\begingroup\$ @Vogel612 It's exactly the point! I know that rule! It was just a typo fixed that does not affect the scope of the review. \$\endgroup\$ – Igor Mar 22 '20 at 3:34
5
\$\begingroup\$

I'd suggest:

public static class AttributeExtensions
{
    public static string GetMyAttributeNameOfProperty<TClass>(string propertyName)
    {
        var propertyInfo = typeof(TClass).GetProperty(propertyName);
        return propertyInfo?.GetCustomAttribute<MyAttribute>()?.Name ?? default;
    }
}

Usage:

var awesome = AttributeExtensions.GetMyAttributeNameOfProperty<Matrix>(nameof(Matrix.AwesomeName));
Console.WriteLine(awesome);

Note: The above isn't an extension method. The below code is.

public static class AttributeExtensions
{
    public static string GetMyAttributeNameOfProperty<TType>(this TType me, string propertyName) where TType : Type
    {
        var propertyInfo = me.GetProperty(propertyName);
        return propertyInfo?.GetCustomAttribute<MyAttribute>()?.Name ?? default;
    }
}

Usage:

var awesome = typeof(Matrix).GetMyAttributeNameOfProperty(nameof(Matrix.AwesomeName));
Console.WriteLine(awesome);

Both output:

awesome_name

Benefits:

  • Does not use an Expression or even the namespace System.Linq.Expressions
  • Won't throw an exception (returns null if property or attribute doesn't exist)
  • Using nameof makes refactoring safe in the event you rename class properties
  • Does not use Func<TClass, object> or similar
  • Does not require an instance of Matrix (or any other type since it's generic)

Either way works and is really a matter of preference. There are pros and cons to using extension methods.

\$\endgroup\$
5
  • \$\begingroup\$ Thanks @Zer0! We make this on the past, we try to simplify our call so we don't have to use typeof(Matrix) and nameof(Matrix.AwesomeName) this make it more verbose... +1 \$\endgroup\$ – Igor Mar 18 '20 at 19:08
  • 2
    \$\begingroup\$ @Igor Using Func<TClass, Object> without Expression is not going to help since you'll get the value of AwesomeName and we only care about the type. I could write a method with the signature Func<TClass, Type> and calling it with exp => exp.AwesomeName.GetType() if you want. But I don't find that any less verbose than the above suggestions. If you really want to avoid typeof, nameof and GetType() I'd stick with the method signature you already have. \$\endgroup\$ – Zer0 Mar 18 '20 at 19:33
  • \$\begingroup\$ This is the way... Cheers! \$\endgroup\$ – Igor Mar 19 '20 at 15:40
  • 1
    \$\begingroup\$ @Igor Glad to hear it. Please mark as answer if you think this solved your question. I can edit in the Func<TClass, Type> solution if you'd like. \$\endgroup\$ – Zer0 Mar 21 '20 at 23:05
  • \$\begingroup\$ Hey @Zer0, when I comment: "This is the way" I meant this part: " I'd stick with the method signature you already have." \$\endgroup\$ – Igor Mar 22 '20 at 3:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.