0

I've created a menu option class for a console application and want to bind the execution method to the option so when the user makes the selection I can do something like selectedOption.Execute(). The option's execute method would be bound to the appropriate method in a utility class. Is this possible? This is what I have so far.

MenuOption Class

public class MenuOption
{
    public MenuOption()
    {
    }

    public MenuOption(ConsoleKey key, string optionLabel, string optionText)
    {
        Key = key;
        OptionLabel = optionLabel;
        OptionText = optionText;
    }

    public MenuOption(ConsoleKey key, string optionLabel, string optionText, List<MenuOption> subMenu)
    {
        Key = key;
        OptionLabel = optionLabel;
        OptionText = optionText;
        SubMenu = subMenu;
    }
    public ConsoleKey? Key { get; set; } = null;
    public string OptionLabel { get; set; } = string.Empty;
    public string OptionText { get; set; } = string.Empty;
    public List<MenuOption> SubMenu { get; set; } = new List<MenuOption>();
    public string Display()
    {
        StringBuilder menu = new StringBuilder();
        menu.AppendLine("Press the number associated with the option of your choice or [Esc] to exit.");
        foreach (MenuOption option in SubMenu)
        {
            if (option.Key == ConsoleKey.Escape)
            {
                menu.AppendLine($"{option.OptionLabel} {option.OptionText}");
            }
            else
            {
                menu.AppendLine($"{option.OptionLabel}. {option.OptionText}");
            }
        }
        return menu.ToString();
    }
}
John Butler
  • 121
  • 6
  • 2
    Do you know what a "delegate" is? – dan04 Mar 21 '23 at 21:29
  • @dan04 `delegate` is just such a non-obvious name for them though - I don't know why they couldn't have called them _function-references_, which is what they are. – Dai Mar 21 '23 at 21:30
  • 2
    @Dai: I think the name comes from the idea of "delegating" work to a helper function/object. Anyhow, C# delegates are *like* C/C++ function pointers, but smarter. And [`Action`](https://learn.microsoft.com/en-us/dotnet/api/system.action?view=net-7.0) and [`Func`](https://learn.microsoft.com/en-us/dotnet/api/system.func-1?view=net-7.0) are predefined generic delegate types. – dan04 Mar 21 '23 at 21:37
  • @dan04 Unfortunately `Func` isn't implicitly convertible with the older `Predicate` type - and `Action` _should not need to exist_ because we _should_ be able to use `void` for `TResult` in `Func<>`... but we can't, _and_ none of the predefined generic delegate types support `out` and `ref` parameters - let alone variadic type-parameterisation. If I'm being honest, I'll say the C++ folks' functor-types are a better deal. – Dai Mar 21 '23 at 21:56
  • @Dai Because they aren't just function pointers, they also encapsulate object state. Hence they are objects that "delegate". – Kieran Devlin Mar 21 '23 at 22:26
  • @KieranDevlin Yes, a `delegate` instance can have a bound `this` parameter, but I'm hesistant to refer to that as "_encapsulating_ object state". – Dai Mar 21 '23 at 22:29

2 Answers2

2

Yes, you can do that using a delegate or an Action (which is a delegate, but is provided by C# so you don't have to declare one). You can check how to use delegates, but I'll use Actions here.

  1. Add an Action property to the MenuOption class that represents the execution method.
  2. Modify the constructors to accept the execution method as a parameter.

Ex.

using System;
using System.Collections.Generic;
using System.Text;

public class MenuOption
{
    public MenuOption() { }

    public MenuOption(ConsoleKey key, string optionLabel, string optionText, Action action)
    {
        Key = key;
        OptionLabel = optionLabel;
        OptionText = optionText;
        Action = action;
    }

    public MenuOption(ConsoleKey key, string optionLabel, string optionText, List<MenuOption> subMenu, Action action)
    {
        Key = key;
        OptionLabel = optionLabel;
        OptionText = optionText;
        SubMenu = subMenu;
        Action = action;
    }

    public ConsoleKey? Key { get; set; } = null;
    public string OptionLabel { get; set; } = string.Empty;
    public string OptionText { get; set; } = string.Empty;
    public List<MenuOption> SubMenu { get; set; } = new List<MenuOption>();

    // Action property to represent the execution method
    public Action Action { get; set; }

    public string Display()
    {
        var menu = new StringBuilder();
        menu.AppendLine("Press the number associated with the option of your choice or [Esc] to exit.");
        
        foreach (var option in SubMenu)
        {
            menu.AppendLine(option.Key == ConsoleKey.Escape
                ? $"{option.OptionLabel} {option.OptionText}"
                : $"{option.OptionLabel}. {option.OptionText}");
        }
        
        return menu.ToString();
    }

    // Execute method to call the bound action
    public void Execute()
    {
        Action?.Invoke();
    }
}

As for how to use it, it would look something like this:

MenuOption option1 = new MenuOption(ConsoleKey.A, "A", "Option 1", UtilityClass.SomeMethod);

Note that UtilityClass.SomeMethod doesn't have () as we're not executing the method, we're just passing the pointer to the function.

You can also use a Lambda (anonymous function) if you want.

MenuOption option1 = new MenuOption(ConsoleKey.A, "A", "Option 1", () => Console.WriteLine("Option 1 selected"));
0

A delegate is a type that represents references to methods with a particular parameter list and return type. Delegates are used to pass methods as arguments to other methods. Any method from any accessible class or struct that matches the delegate type can be assigned to the delegate.

For example, Action Delegate encapsulates a method that has no parameters and does not return a value.

.net has a special types for different delegates:

  1. The Func delegate takes zero, one or more input parameters, and returns a value (with its out parameter).
  2. Action takes zero, one or more input parameters, but does not return anything.
  3. Predicate is a special kind of Func. It represents a method that contains a set of criteria mostly defined inside an if condition and checks whether the passed parameter meets those criteria or not.

For example, you can add an Action type parameter to the MenuOption class constructor and call it where needed.

public class MenuOption
{
    public MenuOption()
    {
    }

    public MenuOption(ConsoleKey key, string optionLabel, string optionText, Action execute)
    {
        Key = key;
        OptionLabel = optionLabel;
        OptionText = optionText;
        Execute = execute;
    }

    public MenuOption(ConsoleKey key, string optionLabel, string optionText, List<MenuOption> subMenu, Action execute)
    {
        Key = key;
        OptionLabel = optionLabel;
        OptionText = optionText;
        SubMenu = subMenu;
        Execute = execute;
    }

    public ConsoleKey? Key { get; set; } = null;
    public string OptionLabel { get; set; } = string.Empty;
    public string OptionText { get; set; } = string.Empty;
    public List<MenuOption> SubMenu { get; set; } = new List<MenuOption>();
    public Action Execute { get; set; }

    public string Display()
    {
        // ...
    }
}

Here is an example of creating a MenuOption with Action parameter. You can call action just like a simple method:

var option = new MenuOption(ConsoleKey.A, "A", "Print text", () => Console.WriteLine("Hello world"));
option.Execute(); // Execute property is public and has Action type so you can call it without parameters

You can also add an existing method as a parameter:

public class Utility
{
    public static void MyMethod()
    {
        Console.WriteLine("Hello from MyMethod!");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var option = new MenuOption(ConsoleKey.A, "A", "Execute MyMethod", Utility.MyMethod);
    }
}
Vadim Martynov
  • 8,602
  • 5
  • 31
  • 43