0

Although attributes applied to any class can be queried via reflection, there is no way to determine which constructor overload was called at compile time. In my scenario, I am analyzing all types in an assembly that have the attribute [TestAttribute] applied as below.

[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class TestAttribute: Attribute
{
    public string Name { get; } = "";
    public string NameDisplay { get; } = "";
    public string Description { get; set; } = "";

    public TestAttribute () { }
    public TestAttribute (string name) { this.Name = name; }
    public TestAttribute (string name, string nameDisplay) : this (name) { this.NameDisplay = nameDisplay; }
    public TestAttribute (string name, string nameDisplay, string description) : this (name, nameDisplay) { this.Description = description; }
}

[TestAttribute()]
[TestAttribute ("_Name")]
[TestAttribute ("_Name_", "_NameDisplay_")]
[TestAttribute ("_Name_", "_NameDisplay_", "_Description_")]
public class TestClass { }

The objective is to generate the code that declares the class TestClass with the correct attributes applied, and emit that into a new assembly. Now, since the attribute declaration syntax is lost to the runtime, I can only query attribute properties. However, notice how some of the attribute properties do not have setters. Also, there is no correlation between the names and order of constructor parameters and the internal properties that they set.

var type = typeof (TestClass);
var attributes = type.GetCustomAttributes(typeof(TestAttribute), inherit: false).Cast<Attribute>();

foreach (var attribute in attributes)
{
    var attributeType = attribute.GetType();
    var constructors = attributeType.GetConstructors().OrderByDescending (c => c.GetParameters().Length);
    var attributeName = attributeType.Name.Substring (0, attributeType.Name.Length - nameof (Attribute).Length);
    var properties = attributeType
        .GetProperties (BindingFlags.Instance | BindingFlags.Public)
        .Where (p => p.CanRead)
        .Where (p => p.GetGetMethod (nonPublic: false) != null)
        .Where (p => ((p.PropertyType.IsValueType) || (p.PropertyType == typeof (string))))
        .ToList();

    foreach (var constructor in constructors)
    {
        var parameters = constructor.GetParameters().ToList();

        if (parameters.Any())
        {
            // Should write: [TestAttribute ("_Name_", "_NameDisplay_", "_Description_")]
            Console.WriteLine($@"[{attributeName} (???????)]");
        }
        else
        {
            // Correct.
            Console.WriteLine($@"[{attributeName}]");
        }
    }
}

Short of a brute approach of trying all permutations with error handling, how could I reliably recreate the code as follows:

[TestAttribute()]
[TestAttribute ("_Name")]
[TestAttribute ("_Name_", "_NameDisplay_")]
[TestAttribute ("_Name_", "_NameDisplay_", "_Description_")]
public class TestClass { }

Any advice would be appreciated.

Raheel Khan
  • 14,205
  • 13
  • 80
  • 168
  • 1
    Have you had a look into the `CustomAttributeData` class? There you can obtain information about the used constructor, named and positional parameters and their values. Start with `CustomAttributeData.GetCustomAttributes(...)` – thehennyy Dec 13 '21 at 09:21
  • 1
    @thehennyy: That worked. `typeof (TestClass).GetCustomAttributesData().ConstructorArguments` and `typeof (TestClass).GetCustomAttributesData().NamedArguments` give me the positions, names and values. You should definitely post this as an answer. – Raheel Khan Dec 14 '21 at 04:00

1 Answers1

1

The System.Reflection.CustomAttributeData class provides information about used constructors, named and positional parameters of CustomAttributes.

You can use CustomAttributeData.GetCustomAttributes(...) or typeof(...).GetCustomAttributesData() as a starting point.

thehennyy
  • 4,020
  • 1
  • 22
  • 31