Search code examples
c#reflectionoverload-resolution

How to determine the best-matched constructor, given a set of properties


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.


Solution

  • 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.