1

I want to read the parameters of attributes (e.g. [MyAttribute("val1", "val2"]) which are attached to a class or to a method, or want to get back the attribute object of a particular class or method. This seems to be not available for all cases.

While I was trying to implement it, i.e.

  1. Read parameters of an attribute, attached to a class. How? (see Question 1)
Parameters are the constructor parameters of the attribute, e.g.
public MyOwnAttribute(params object[] p).

  2. Get attibute object's properties, attached to a method. How? (see Question 2)
In the example attribute MyOwnAttribute, there are MyProperty1 and MyProperty2 defined. So if you apply it to
[MyOwnAttribute("World", "2")]
public void MyMethod2() {...},
how can I read them (the constructor assigns "World" to MyProperty1 and "2" to MyProperty1)?

  3. Read parameters of an attribute, attached to a method. Solved. Example: MyOwnAttribute.GetMethodAttributeValues<clsA>(nameof(clsA.MyMethod2))

  4. Get attribute object's properties, attached to a class. Solved. Example: MyOwnAttribute.GetClassAttributes(typeof(clsA)).MyProperty1

I noticed that I couldn't implement it for all 4 cases, I couldn't implement it for cases 1. and 4. The code I wrote for cases 2. and 3. is working fine (I removed it since you now have Tobias' answer for all 4 cases). The SO sidebar shows some interesting links ("Related": Link 1, Link 2, Link 3), but still I couldn't put the missing pieces together.

Consider the following custom attribute:

public class MyOwnAttribute : Attribute
{

    public string MyProperty1 { get; set; }
    public string MyProperty2 { get; set; }

    // simple case, to make explaining it easier
    public MyOwnAttribute(string p1 = null, string p2 = null)
    {
        MyProperty1 = p1?.ToString();
        MyProperty2 = p2?.ToString();
    }

}

Or I could have the properties defined like:

    public string[] MyProperties { get; set; }

    // allows any number of properties in constructor
    public MyOwnAttribute(params object[] p)
    {
        MyProperties = p.Select(s => s.ToString()).ToArray();
    }

But I couldn't figure out how you could implement this:

  • Question 1: How can I access the list of attribte's parameter values attached to a class?
    (from the attribute's constructor, like implemented for the methods)
    Example:
    var classAttribParamValues = MyOwnAttribute.GetClassAttributeValues(typeof(clsA));
    string.Join(' ', classAttribParamValues.Select(s => s ?? "")).Dump();

  • Question 2: How can I access attribute's properties attached to a method?
    (like implemented for the class)
    Example:
    var methodInfo = MyOwnAttribute.GetMethodAttributes<clsA>(nameof(clsA.MyMethod2));
    methodInfo.MyProperty1.Dump();

Does anyone have an idea how that can be done? Many thanks in advance.

Matt
  • 25,467
  • 18
  • 120
  • 187
  • PropertyInfo class for properties and Assembly class for method metadata – GH DevOps Oct 08 '21 at 13:30
  • Added samples to the 2 questions so you can better see what I am asking ... – Matt Oct 08 '21 at 13:51
  • 1
    I reread this question multiple times but still have no idea what you need. Maybe clarify a bit with an example. "How can I access the list of attribte's parameter values attached to a class" - seems you already do that in your code with `clsInfo.MyProperty1.Dump();`... – Evk Oct 12 '21 at 17:14
  • I suppose the line stating ".Where(w => w.GetType() == typeof(MyOwnAttribute)))" should be changed to ".Where(w => w.GetType() == typeof(T)))" instead? And how did you manage to create a generic class deriving from Attribute, per the spec that's not allowed. – Tobias Schulte Oct 13 '21 at 05:51
  • @Evk - I hope that the question is better structured now. – Matt Oct 13 '21 at 07:53
  • @TobiasSchulte - Yes, you are right. It is `typeof(T)`, fixed it in the code. Regarding "how did you manage to create a generic class deriving from Attribute": I tested that in Linqpad 6 with latest Roslyn code turned on (Roslyn 4.0.0-5.21452.18), and there it is working. But I noticed also that DotNetFiddle does not allow it - in that case the `T` needs to be moved to the methods (away from the class, so the class is no more generic, only the methods). But I suppose it will be allowed in the future in C#. And it has some advantages to use it. – Matt Oct 13 '21 at 07:59
  • 1
    I don't understand the difference between "Get attibute object's properties, attached to a class" and "Read parameters of an attribute, attached to a class. How?". You already got attribute object attached to a class, you have access to it's properties. Which "parameters" you mean? – Evk Oct 13 '21 at 08:59
  • @Evk - An attribute is a class derived from `Attribute`, i.e. MyOwnAttribute in the example. And if you define properties there, you can read them later on (see my example). Their values are assigned in the constructor. In contrast, the parameters are the parameters of the constructor of the MyOwnAttribute class, which are being read by using reflection. Both are different ways of reading what you defined in an attribute, e.g. `[MyOwnAttribute("World", "2")] public void MyMethod2() { ... }`. You want to read "World" and "2". – Matt Oct 13 '21 at 11:40
  • Ok I finally get it. Well, fortunately there is already valid answer now it seems :) – Evk Oct 13 '21 at 11:58
  • @Evk - I made the question a bit shorter, so future readers hopefully faster get to the point. The answer Tobias has provided works fine btw. – Matt Oct 13 '21 at 13:12

1 Answers1

3

Edit:

After some clarifications here's what I suspect you want to have:

public static class AttributeHelper {

    public static TAttribute GetClassAttribute<TTarget, TAttribute>() where TAttribute : Attribute
        => typeof(TTarget).GetAttribute<TAttribute>();

    public static object[] GetClassAttributeValues<TTarget, TAttribute>() where TAttribute : Attribute
        => typeof(TTarget).GetAttributeValues<TAttribute>();

    public static TAttribute GetMethodAttribute<TTarget, TAttribute>(string methodName) where TAttribute : Attribute
        => typeof(TTarget).GetMethod(methodName)?.GetAttribute<TAttribute>();

    public static object[] GetMethodAttributeValues<TTarget, TAttribute>(string methodName) where TAttribute : Attribute
        => typeof(TTarget).GetMethod(methodName)?.GetAttributeValues<TAttribute>();

    private static TAttribute GetAttribute<TAttribute>(this MemberInfo memberInfo) where TAttribute : Attribute
        => memberInfo?.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault();

    private static object[] GetAttributeValues<TAttribute>(this MemberInfo memberInfo) where TAttribute : Attribute
        => memberInfo
            ?.GetCustomAttributesData()
            .FirstOrDefault(d => d.AttributeType == typeof(TAttribute))
            ?.ConstructorArguments
            .Select(argument => argument.Value)
            .SelectMany(a => a is ReadOnlyCollection<CustomAttributeTypedArgument> p ? p.Select(c => c.Value) : new[] { a })
            .ToArray();
}

The methods are all static now and AttributeHelper no longer derives from Attribute, as you would have to provide the attribute type anyway as a parameter to the methods. As a second parameter simply provide the class on which you expect the attribute.

I took some time to reorganize the code as it was quite hard to read for me. Perhaps it's a little bit easier to read for you as well.

Example calls:

AttributeHelper.GetClassAttributeValues<clsA, MyOwnAttribute>().Dump("1.");

AttributeHelper.GetMethodAttribute<clsA, 
                        MyOwnAttribute>(nameof(clsA.MyMethod2)).Dump("2.");

AttributeHelper.GetMethodAttributeValues<clsA,
                        MyOwnAttribute>(nameof(clsA.MyMethod2)).Dump("3.");

AttributeHelper.GetClassAttribute<clsA,MyOwnAttribute>().Dump("4.");

Old Answer below:

I think what you are trying to achieve could be solved with an additional protected abstract method in AttributeHelper with a return type of string[], this would make AttributeHelper abstract as well, but that should be no problem.

Your deriving Attributes would be forced to implement this new method (if they are not abstract themselves). The Implementation would have to return an array of the values of the attributes properties.

Another approach could be to iterate over the PropertyInfos of your Attribute type and read the Properties that way.

Matt
  • 25,467
  • 18
  • 120
  • 187
Tobias Schulte
  • 716
  • 5
  • 18
  • Tobias, which of the 2 questions are you referring to? Question 1, I assume? Then it would be a workaround like using implementation 4. and enforcing a property list by introducing an abstract method. – Matt Oct 13 '21 at 08:48
  • Have you thought about Question 2, too (how to access attribute's properties of a method)? – Matt Oct 13 '21 at 08:56
  • 1
    @Matt I think I know now what you want to achieve, that answer is adjusted accordingly. – Tobias Schulte Oct 13 '21 at 10:08
  • Many thanks this is perfectly working! Now MyOwnAttribute derives directly from Attribute and I put samples into your answer. And you will get the bounty too, as soon as I am allowed to click ... ;-) – Matt Oct 13 '21 at 12:41