Вызов методов в зависимости от значения ComboBox

313
23 декабря 2017, 02:53

В коде есть много вычислений, в зависимости от начальных данных я получаю разный результат. Начальные данные определяются значением ComboBox, и в итоге в зависимости от этого, к переменной присваивается один из методов внешней библиотеке. Как лучше реализовать это? В итоге, думаю, в зависимости от Combobox приравнивать переменной string название метода в библиотеке и использовать reflection. И в коде, всё, что мне нужно будет менять, это перебор значений Combobox через switch и присваивание названия методов. Потому что в ComboBox в итоге будет значений 30-40. Нужно что-то универсальное и лаконичное.

Answer 1

Заведите интерфейс:

public interface IOperation
{
    int Execute(int p1, int p2);
}

Вместо статических методов создайте классы, реализующие его:

public class AddOperation : IOperation
{
    public int Execute(int p1, int p2)
    {
        return p1 + p2;
    }
}
public class MulOperation : IOperation
{
    public int Execute(int p1, int p2)
    {
        return p1 * p2;
    }
}

Тогда, получить список классов, реализующих заданный интерфейс можно примерно так:

var operationTypes =
    typeof(Program)
        .Assembly.GetTypes()
        .Where(t => t.GetInterfaces().Contains(typeof(IOperation)))
        .ToList();

Потом, после выбора нужной операции, создаем ее экземпляр и запускаем:

var operation = (IOperation)Activator.CreateInstance(operationTypes[0]);
Console.WriteLine(operation.Execute(1, 2));

Минус этого подхода (как по мне - незначительный) - эти классы с операциями должны иметь конструктор без параметров.

Другой способ - через рефлексию получить все методы класса/сборки, для того чтобы нужные методы отличить от других можно завести кастомный атрибут:

[AttributeUsage(AttributeTargets.Method)]
public class ExecuteAttribute : Attribute { }

Помечаем нужные методы атрибутом:

public static class Library
{
    [Execute]
    public static int SumOperation(int p1, int p2)
    {
        return p1 + p2;
    }
    [Execute]
    public static int MulOperation(int p1, int p2)
    {
        return p1 * p2;
    }
}

Получаем список:

var methods = 
    typeof(Library)
        .GetMethods()
        .Where(m => m.GetCustomAttribute<ExecuteAttribute>() != null)
        .ToList();

Вызываем нужный:

Console.WriteLine((int)methods[0].Invoke(null, new object[] { 1, 2 }));

Минус этого похода - нет защиты от того, что у метода может быть другая сигнатура и это приведет к ошибке во время исполнения.

При желании в атрибуте можно сделать свойство для указания наименования метода:

[AttributeUsage(AttributeTargets.Method)]
public class ExecuteAttribute : Attribute
{
    public string Name { get; }
    public ExecuteAttribute(string name)
    {
        Name = name;
    }
}

Тогда, получить имя можно будет так:

var method = methods[0];
string name = method.GetCustomAttribute<ExecuteAttribute>().Name;

А задавать: [Execute("Сложить")]

Ну и для удобства вызова метода можно создать на его основе делегат (спасибо @VladD за подсказку):

var operation = (Func<int, int, int>)Delegate.CreateDelegate(typeof(Func<int, int, int>), method);
Console.WriteLine($"{name} 1 и 2 будет {operation(1, 2)}");
Answer 2

Я бы сделал такую структуру данных:

public class MethodDescription
{
    public string Name { get; }
    public Action Method { get; }
    public MethodDescription(string name, Action method)
    {
        Name = name;
        Method = method;
    }
}

Теперь, раз внешняя сборка в ваших руках, она может легко предоставить список всех нужных методов. Например, так:

public static class Methods
{
    public static IEnumerable<MethodDescription> All { get; } =
        new[]
        {
            new MethodDescription("First", MyClass.First),
            new MethodDescription("Second", MyClass.Second),
            new MethodDescription("Very big", MyClass.VeryBig),
            new MethodDescription("Not at all", MyClass.NotAtAll)
            // добавляйте сюда ещё
        };
}

и так далее. Можно также собирать методы не вручную, а через рефлексию.

На клиентской стороне всё получается просто:

<ComboBox ItemsSource="{Binding Source={x:Static extAssembly:Methods.All}}"
          DisplayMemberName="Name"/>
READ ALSO
Поставить маркер на PictureBox.Image (C#, VS, WF)

Поставить маркер на PictureBox.Image (C#, VS, WF)

Как прикрепить маркер (тоже Image) к PicureBoxImage?

263
Как вывести сообщение в консоль из приложения Windows Forms?

Как вывести сообщение в консоль из приложения Windows Forms?

Приложение может запускаться с параметрамиЕсть необходимость вывести хелп с параметрами в этой же консоли, где было запущено приложение,...

287
Расположить один объект рядом с другим

Расположить один объект рядом с другим

Необходимо расположить Объект1 рядом с Объект2

267
Отобразить PNG из API во View

Отобразить PNG из API во View

Получаю через API картинку PNG в контроллере

201