Search code examples
c#winformsfor-loopdynamic-function

Dynamically call a function in Windows Form


I'm building an app to test some equipment and the app will have 43 test steps (Step01.cs to Step43.cs). (tests are massive and need to be split in separate files)

In each .cs file there is a public static void Test(){} function.

At any given point the user can go back and redo a test and at the end of each test the user is asked if he wants to redo the next step(only if it has been previously done). If next step has never been done, it continues with the test as usual.

if (currentStep < maxStep )
{ 
    for(int i = currentStep; i < maxStep; i++)
    {
        _form1.testNumberComboBox.SelectedIndex = i - 1;

        if (MessageBox.Show($"{_form1.testNameComboBox.SelectedItem.ToString()} has already been tested.\nWould you like to retest?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
        {
            Step02.Test(_form1, sensor);
            return;
        }
    }
    _form1.testNumberComboBox.SelectedIndex = maxStep - 1;
}

My question is if it possible to do something like this Step{i}.Test(_form1, sensor); to call what test I need, as I don't really want to do if(i == 2){Step02.Test(_form1, sensor);}...if(i == 40){Step40.Test(_form1, sensor);} if the answer from the user is Yes.

I've done something like this in PHP a while back. Had variables $acc1, $acc2 ... $accX and was able to call them in a for(i) loop with ${"acc$i"}.

I'm not sure if it is possible in C# and that is why I'm asking. (I'm new to C#)


Solution

  • You can use Reflection or use a Dictionary<string, Action>.

    Assuming you have test classes like this:

    namespace MyTests
    {    
        public class Test1
        {
            public static void Test() { }
        }
    }
    

    Call the test using Reflection:

    Find the class and the method using reflection and call it:

    Assembly.GetExecutingAssembly().GetType("MyTests.Test1")
        .GetMethod("Test").Invoke(null, null);
    

    Call the test using Dictionary<string, Action>:

    Initialize a Dictionary<string, Action>() like this once:

    Dictionary<string, Action> tests = new Dictionary<string, Action>();
    tests.Add("Test1", () => Test1.Test());
    tests.Add("Test2", () => Test2.Test());
    

    Then call by name whenever you need:

    tests["Test1"]();
    

    Combine both reflection and Dictionary<string, Action>:

    Or you can combine both for better performance and easier init. Find all test in startup using reflection once:

    Dictionary<string, Action> tests = Assembly.GetExecutingAssembly().GetTypes()
        .Where(t => t.FullName.StartsWith("MyTests.Test"))
        .Select(t => t.GetMethod("Test"))
        .ToDictionary(m => m.DeclaringType.Name,
            m => new Action(() => m.Invoke(null, null)));
    

    Run whenever you need:

    tests["Test1"]();
    tests["Test2"]();