Use reflection to get attribute of a property via

2020-02-29 02:53发布

问题:

Note: This is a follow-up to an answer on a previous question.

I'm decorating a property's setter with an Attribute called TestMaxStringLength that's used in method called from the setter for validation.

The property currently looks like this:

public string CompanyName
{
    get
    {
        return this._CompanyName;
    }
    [TestMaxStringLength(50)]
    set
    {
        this.ValidateProperty(value);
        this._CompanyName = value;
    }
}

But I would rather it look like this:

[TestMaxStringLength(50)]
public string CompanyName
{
    get
    {
        return this._CompanyName;
    }
    set
    {
        this.ValidateProperty(value);
        this._CompanyName = value;
    }
}

The code for ValidateProperty that is responsible for looking up the attributes of the setter is:

private void ValidateProperty(string value)
{
    var attributes = 
       new StackTrace()
           .GetFrame(1)
           .GetMethod()
           .GetCustomAttributes(typeof(TestMaxStringLength), true);
    //Use the attributes to check the length, throw an exception, etc.
}

How can I change the ValidateProperty code to look for attributes on the property instead of the set method?

回答1:

As far as I know, there's no way to get a PropertyInfo from a MethodInfo of one of its setters. Though, of course, you could use some string hacks, like using the name for the lookup, and such. I'm thinking something like:

var method = new StackTrace().GetFrame(1).GetMethod();
var propName = method.Name.Remove(0, 4); // remove get_ / set_
var property = method.DeclaringType.GetProperty(propName);
var attribs = property.GetCustomAttributes(typeof(TestMaxStringLength), true);

Needless to say, though, that's not exactly performant.

Also, be careful with the StackTrace class - it's a performance hog, too, when used too often.



回答2:

In the class that declares the method, you could search for the property that contains that setter. It's not performant, but neither is StackTrace.

void ValidateProperty(string value)
{
    var setter = (new StackTrace()).GetFrame(1).GetMethod();

    var property = 
        setter.DeclaringType
              .GetProperties()
              .FirstOrDefault(p => p.GetSetMethod() == setter);

    Debug.Assert(property != null);

    var attributes = property.GetCustomAttributes(typeof(TestMaxStringLengthAttribute), true);

    //Use the attributes to check the length, throw an exception, etc.
}


回答3:

You could consider, as an alternative approach, delaying validation until later, thus removing the need to inspect the stack trace.

This example provides an attribute...

public class MaxStringLengthAttribute : Attribute
{
    public int MaxLength { get; set; }
    public MaxStringLengthAttribute(int length) { this.MaxLength = length; }
}

... a POCO with the attribute applied to a property...

public class MyObject
{
    [MaxStringLength(50)]
    public string CompanyName { get; set; }
}

... and a utility class stub that validates the object.

public class PocoValidator
{
    public static bool ValidateProperties<TValue>(TValue value)
    {
        var type = typeof(TValue);
        var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            var atts = prop.GetCustomAttributes(typeof(MaxStringLengthAttribute), true);
            var propvalue = prop.GetValue(value, null);

            // With the atts in hand, validate the propvalue ...
            // Return false if validation fails.
        }

        return true;
    }
}