Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pull all constructor options recursively?

Let's say I have an object containing an object each with one or more constructors:

 Foo(int p1, Bar p2);
 
 Bar(String p3, String p4);

 Bar(int p5, int p6);    

And I want to display say in a UI what parameters are required to build a Foo. I think there are three options:

int p1, Bar p2

Or

int p1, String p3, String p4

Or

int p1, int p5, int p6

Obviously, it gets more complex as there are more objects nested. Is there a way to pull all of these options? How about in C#? Bonus points if you can show how to build a Foo dynamically having been given option 2 or 3 above.

like image 652
Seth Kitchen Avatar asked Jan 25 '23 14:01

Seth Kitchen


1 Answers

You can (as suggested in comments) use reflection and recursion to achieve what you want. It's up to you what is the criteria to descend into one more level of recursion. I've implemented these classes to mimic yours:

public class Foo
{
    public Foo(int p1, Bar p2)
    { }
}
public class Bar
{
    public Bar(string p3, string p4)
    { }
    public Bar(int p5, int p6)
    { }
}

Then I've implemented this recursive method to print the possible constructs:

public static void PrintConstructors(Type t, string currentOutput)
{
    foreach (var ctor in t.GetConstructors())
    {
        var construct = currentOutput;
        foreach (var param in ctor.GetParameters())
        {
            if (param.ParameterType.IsClass && param.ParameterType != typeof(string))
            { PrintConstructors(param.ParameterType, construct); }
            construct += $"{param.ParameterType} {param.Name}, ";
        }
        if (!string.IsNullOrEmpty(construct))
        { Console.WriteLine(construct.TrimEnd(',', ' ')); }
    }
}

Calling PrintConstructors(typeof(Foo), ""); produces the following output:

System.Int32 p1, System.String p3, System.String p4
System.Int32 p1, System.Int32 p5, System.Int32 p6
System.Int32 p1, ConsoleApp.Bar p2

EDIT: An alternative when having more than one complex type in a constructor would be (without checking for circular dependencies as @Michał Turczyn stated):

public static IEnumerable<string> GetConstructs(Type t)
{
    foreach (var ctor in t.GetConstructors())
    {
        var constructs = new List<string>() { "" };
        foreach (var param in ctor.GetParameters())
        {
            if (param.ParameterType.IsClass && param.ParameterType != typeof(string))
            {
                var newConstructs = new List<string>();
                foreach (var _construct in GetConstructs(param.ParameterType))
                {
                    foreach (var construct in constructs)
                    { newConstructs.Add(construct + $"{param.ParameterType } ({_construct}) {param.Name}, "); }
                }
                constructs = newConstructs;
            }
            else
            {
                for (var i = 0; i < constructs.Count; i++)
                { constructs[i] += $"{param.ParameterType} {param.Name}, "; }
            }

        }
        foreach (var construct in constructs)
        { yield return construct.TrimEnd(',', ' '); }
    }
}

Assuming you update the constructor of Foo(int p1, Bar p2) to Foo(int p1, Bar p2, string s1, Bar y) the output you get from:

foreach (var construct in GetConstructs(typeof(Foo)))
{ Console.WriteLine(construct); }

is:

System.Int32 p1, ConsoleApp.Bar (System.String p3, System.String p4) p2, System.String s1, ConsoleApp.Bar (System.String p3, System.String p4) y
System.Int32 p1, ConsoleApp.Bar (System.Int32 p5, System.Int32 p6) p2, System.String s1, ConsoleApp.Bar (System.String p3, System.String p4) y
System.Int32 p1, ConsoleApp.Bar (System.String p3, System.String p4) p2, System.String s1, ConsoleApp.Bar (System.Int32 p5, System.Int32 p6) y
System.Int32 p1, ConsoleApp.Bar (System.Int32 p5, System.Int32 p6) p2, System.String s1, ConsoleApp.Bar (System.Int32 p5, System.Int32 p6) y
like image 130
dcg Avatar answered Feb 07 '23 17:02

dcg