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

4
@Jodrell, (продовження) Якщо ви хочете максимально використати своє обладнання, тоді ви використовуєте ASM, C або C ++ у такому порядку. Пошук словника в .Net вас не вб’є. Різні підписи делегатів - це найсильніший аргумент, який ви зробили проти простого словникового підходу.
Hamish Grubijan 02

Відповіді:


120

Подобається це:

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, див. моє останнє оновлення. Я додав приклад з Dictionary<int, Delegate>.
Дарін Димитров

3
Я не буду downvote, але я просто повинен сказати , що такий родом реалізація є антипаттерн без дуже вагомих причин. Якщо операційна програма очікує, що клієнти передадуть всі аргументи функції, то навіщо спочатку таблиця функцій? Чому б клієнтові просто не викликати функції без магії динамічних викликів ?
Джульєтта

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

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.