C # Хранение функций в словаре


93

Как мне создать словарь, в котором я могу хранить функции?

Спасибо.

У меня около 30+ функций, которые могут быть выполнены пользователем. Я хочу иметь возможность выполнять функцию следующим образом:

   private void functionName(arg1, arg2, arg3)
   {
       // code
   }

   dictionaryName.add("doSomething", functionName);

    private void interceptCommand(string command)
    {
        foreach ( var cmd in dictionaryName )
        {
            if ( cmd.Key.Equals(command) )
            {
                cmd.Value.Invoke();
            }
        }
    }

Однако сигнатура функции не всегда одинакова, поэтому у нее разное количество аргументов.


5
Это отличная идиома - может заменить неприятные операторы переключения.
Hamish Grubijan

У вашей примерной функции есть параметры, однако, когда вы вызываете сохраненную функцию, вы вызываете ее без каких-либо аргументов. Фиксируются ли аргументы при сохранении функции?
Тим Ллойд,

1
@HamishGrubijan, если вы сделаете это для замены оператора switch, тогда вы откажетесь от всей оптимизации и ясности времени компиляции. Так что я бы сказал, что это скорее идиот, чем идиома. Если вы хотите динамически отображать функциональность таким образом, чтобы она могла изменяться во время выполнения, это может быть полезно.
Джодрелл

12
@Jodrell, A) Идиот - сильное неконструктивное слово, B) Ясность в глазах смотрящего. Я видел множество уродливых операторов переключения. C) Оптимизация времени компиляции ... Zooba в этих потоках приводит аргументы в пользу противоположного stackoverflow.com/questions/505454/. Если операторы switch - O (log N), тогда для скорости, чтобы иметь значение, потребуется более 100 случаев. . Это, конечно, не читается. Возможно, оператор switch может использовать идеальную хеш-функцию, но только для небольшого количества случаев. Если вы используете .Net, то вам не нужно беспокоиться о микросекундах.
Hamish Grubijan 02

4
@Jodrell, (продолжение) Если вы хотите выжать максимум из своего оборудования, используйте ASM, C или C ++ именно в таком порядке. Поиск по словарю в .Net вас не убьет. Различные подписи делегатов - это самый сильный аргумент, который вы сделали против использования простого словарного подхода.
Hamish Grubijan 02

Ответы:


121

Как это:

Dictionary<int, Func<string, bool>>

Это позволяет хранить функции, которые принимают строковый параметр и возвращают логическое значение.

dico[5] = foo => foo == "Bar";

Или, если функция не анонимная:

dico[5] = Foo;

где Foo определяется так:

public bool Foo(string bar)
{
    ...
}

ОБНОВИТЬ:

Увидев ваше обновление, кажется, что вы заранее не знаете сигнатуру функции, которую хотите вызвать. В .NET для вызова функции вам необходимо передать все аргументы, и если вы не знаете, какие аргументы будут, единственный способ добиться этого - через отражение.

А вот еще одна альтернатива:

class Program
{
    static void Main()
    {
        // store
        var dico = new Dictionary<int, Delegate>();
        dico[1] = new Func<int, int, int>(Func1);
        dico[2] = new Func<int, int, int, int>(Func2);

        // and later invoke
        var res = dico[1].DynamicInvoke(1, 2);
        Console.WriteLine(res);
        var res2 = dico[2].DynamicInvoke(1, 2, 3);
        Console.WriteLine(res2);
    }

    public static int Func1(int arg1, int arg2)
    {
        return arg1 + arg2;
    }

    public static int Func2(int arg1, int arg2, int arg3)
    {
        return arg1 + arg2 + arg3;
    }
}

При таком подходе вам все еще нужно знать количество и тип параметров, которые необходимо передать каждой функции по соответствующему индексу словаря, иначе вы получите ошибку времени выполнения. И если ваши функции не имеют возвращаемых значений, используйте System.Action<>вместо System.Func<>.


Понимаю. Думаю, тогда мне придется потратить некоторое время на чтение об размышлениях, благодарю за помощь.
chi

@chi, смотри мое последнее обновление. Я добавил пример с Dictionary<int, Delegate>.
Дарин Димитров

3
Я не буду отрицать, но должен сказать, что такая реализация является антипаттерном без очень веских причин. Если OP ожидает, что клиенты передадут все аргументы функции, тогда зачем вообще нужна таблица функций? Почему бы клиенту просто не вызывать функции без магии динамических вызовов ?
Джульетта,

1
@ Джульетта, я согласен с тобой, я никогда не говорил, что это хорошо. Кстати, в своем ответе я подчеркнул тот факт, что нам все еще нужно знать количество и тип параметров, которые необходимо передать каждой функции по соответствующему индексу словаря. Вы абсолютно правы, указав, что в этом случае нам, вероятно, даже не нужна хеш-таблица, поскольку мы могли бы напрямую вызывать функцию.
Дарин Димитров

1
Что, если мне нужно сохранить функцию, которая не принимает никаких аргументов и не имеет возвращаемого значения?
DataGreed

8

Однако сигнатура функции не всегда одинакова, поэтому у нее разное количество аргументов.

Начнем с нескольких функций, определенных следующим образом:

private object Function1() { return null; }
private object Function2(object arg1) { return null; }
private object Function3(object arg1, object arg3) { return null; }

В вашем распоряжении действительно 2 жизнеспособных варианта:

1) Поддерживайте безопасность типов, позволяя клиентам вызывать вашу функцию напрямую.

Это, вероятно, лучшее решение, если у вас нет очень веские причины для разрыва этой модели.

Когда вы говорите о желании перехватывать вызовы функций, мне кажется, что вы пытаетесь заново изобрести виртуальные функции. Существует множество способов получить такую ​​функциональность из коробки, например, наследование от базового класса и переопределение его функций.

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

public interface IMyObject
{
    object Function1();
    object Function2(object arg1);
    object Function3(object arg1, object arg2);
}

class MyObject : IMyObject
{
    public object Function1() { return null; }
    public object Function2(object arg1) { return null; }
    public object Function3(object arg1, object arg2) { return null; }
}

class MyObjectInterceptor : IMyObject
{
    readonly IMyObject MyObject;

    public MyObjectInterceptor()
        : this(new MyObject())
    {
    }

    public MyObjectInterceptor(IMyObject myObject)
    {
        MyObject = myObject;
    }

    public object Function1()
    {
        Console.WriteLine("Intercepted Function1");
        return MyObject.Function1();
    }
    public object Function2(object arg1)
    {
        Console.WriteLine("Intercepted Function2");
        return MyObject.Function2(arg1);
    }

    public object Function3(object arg1, object arg2)
    {
        Console.WriteLine("Intercepted Function3");
        return MyObject.Function3(arg1, arg2);
    }
}

2) ИЛИ сопоставьте ввод ваших функций с общим интерфейсом.

Это может сработать, если все ваши функции связаны. Например, если вы пишете игру, и все функции что-то делают с какой-то частью игрока или его инвентаря. Вы получите что-то вроде этого:

class Interceptor
{
    private object function1() { return null; }
    private object function2(object arg1) { return null; }
    private object function3(object arg1, object arg3) { return null; }

    Dictionary<string, Func<State, object>> functions;

    public Interceptor()
    {
        functions = new Dictionary<string, Func<State, object>>();
        functions.Add("function1", state => function1());
        functions.Add("function2", state => function2(state.arg1, state.arg2));
        functions.Add("function3", state => function3(state.arg1, state.are2, state.arg3));
    }

    public object Invoke(string key, object state)
    {
        Func<object, object> func = functions[key];
        return func(state);
    }
}

Мое предположение будет objectпохоже на то, которое будет передано в новый поток.
Скотт Фрэйли

1

Эй, надеюсь, это поможет. С какого ты языка?

internal class ForExample
{
    void DoItLikeThis()
    {
        var provider = new StringMethodProvider();
        provider.Register("doSomethingAndGetGuid", args => DoSomeActionWithStringToGetGuid((string)args[0]));
        provider.Register("thenUseItForSomething", args => DoSomeActionWithAGuid((Guid)args[0],(bool)args[1]));


        Guid guid = provider.Intercept<Guid>("doSomethingAndGetGuid", "I don't matter except if I am null");
        bool isEmpty = guid == default(Guid);
        provider.Intercept("thenUseItForSomething", guid, isEmpty);
    }

    private void DoSomeActionWithAGuid(Guid id, bool isEmpty)
    {
        // code
    }

    private Guid DoSomeActionWithStringToGetGuid(string arg1)
    {
        if(arg1 == null)
        {
            return default(Guid);
        }
        return Guid.NewGuid();
    }

}
public class StringMethodProvider
{
    private readonly Dictionary<string, Func<object[], object>> _dictionary = new Dictionary<string, Func<object[], object>>();
    public void Register<T>(string command, Func<object[],T> function)
    {
        _dictionary.Add(command, args => function(args));
    }
    public void Register(string command, Action<object[]> function)
    {
        _dictionary.Add(command, args =>
                                     {
                                         function.Invoke(args);
                                         return null;
                                     } );
    }
    public T Intercept<T>(string command, params object[] args)
    {
        return (T)_dictionary[command].Invoke(args);
    }
    public void Intercept(string command, params object[] args)
    {
        _dictionary[command].Invoke(args);
    }
}

1

Почему бы не использовать params object[] listдля параметров метода и выполнить некоторую проверку внутри ваших методов (или логики вызова), это позволит использовать переменное количество параметров.


1

Следующий сценарий позволит вам использовать словарь элементов для отправки в качестве входных параметров и получить то же самое, что и выходные параметры.

Сначала добавьте следующую строку вверху:

using TFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Collections.Generic.IDictionary<string, object>>;

Затем внутри вашего класса определите словарь следующим образом:

     private Dictionary<String, TFunc> actions = new Dictionary<String, TFunc>(){

                        {"getmultipledata", (input) => 
                            {
                                //DO WORKING HERE
                                return null;
                            } 
                         }, 
                         {"runproc", (input) => 
                            {
                                //DO WORKING HERE
                                return null;
                            } 
                         }
 };

Это позволит вам запускать эти анонимные функции с синтаксисом, подобным следующему:

var output = actions["runproc"](inputparam);

0

Определите словарь и добавьте ссылку на функцию в качестве значения, используя System.Actionв качестве типа:

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

public class Actions {

    public Dictionary<string, System.Action> myActions = new Dictionary<string, System.Action>();

    public Actions() {
        myActions ["myKey"] = TheFunction;
    }

    public void TheFunction() {
        // your logic here
    }
}

Затем вызовите его с помощью:

Actions.myActions["myKey"]();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.