Funkcje C # Store w słowniku

93

Jak utworzyć słownik, w którym mogę przechowywać funkcje?

Dzięki.

Mam około 30+ funkcji, które mogą być wykonane przez użytkownika. Chcę móc wykonać tę funkcję w ten sposób:

   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();
            }
        }
    }

Jednak sygnatura funkcji nie zawsze jest taka sama, przez co ma różną liczbę argumentów.

chi
źródło
5
To świetny idiom - może zastąpić nieprzyjemne instrukcje przełącznika.
Hamish Grubijan
Twoja przykładowa funkcja ma parametry, jednak kiedy wywołujesz zapisaną funkcję, wywołujesz ją bez żadnych argumentów. Czy argumenty są ustalone podczas przechowywania funkcji?
Tim Lloyd,
1
@ HamishGrubijan, jeśli zrobisz to w celu zastąpienia instrukcji switch, odrzucisz całą optymalizację czasu kompilacji i przejrzystość. Więc powiedziałbym, że to bardziej idiota niż idiom. Jeśli chcesz dynamicznie mapować funkcje w sposób, który może się zmieniać w czasie wykonywania, może być przydatny.
Jodrell,
12
@Jodrell, A) Idiota to mocne słowo, które nie jest konstruktywne, B) Czystość jest w oczach patrzącego. Widziałem wiele brzydkich instrukcji przełącznika. C) Optymalizacja czasu kompilacji ... Zooba w tych wątkach argumentuje za przeciwnym stackoverflow.com/questions/505454/ ... Jeśli instrukcja switch to O (log N), to musiałaby zawierać ponad 100 przypadków, aby szybkość miała znaczenie . To oczywiście nie jest czytelne. Może instrukcja switch może wykorzystywać idealną funkcję skrótu, ale tylko w niewielkiej liczbie przypadków. Jeśli używasz .Net, nie martwisz się o mikrosekundy.
Hamish Grubijan
4
@Jodrell, (kontynuacja) Jeśli chcesz wycisnąć jak najwięcej ze swojego sprzętu, użyj ASM, C lub C ++ w tej kolejności. Wyszukiwanie w słowniku w .Net cię nie zabije. Różne podpisy delegatów - to najmocniejszy punkt, który wypowiedziałeś przeciwko stosowaniu prostego podejścia słownikowego.
Hamish Grubijan

Odpowiedzi:

120

Lubię to:

Dictionary<int, Func<string, bool>>

Pozwala to na przechowywanie funkcji, które pobierają parametr łańcuchowy i zwracają wartość logiczną.

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

Lub jeśli funkcja nie jest anonimowa:

dico[5] = Foo;

gdzie Foo jest zdefiniowane w ten sposób:

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

AKTUALIZACJA:

Po obejrzeniu aktualizacji wydaje się, że nie znasz z góry podpisu funkcji, którą chcesz wywołać. W .NET, aby wywołać funkcję, musisz przekazać wszystkie argumenty, a jeśli nie wiesz, jakie będą argumenty, jedynym sposobem na osiągnięcie tego jest odbicie.

A oto inna alternatywa:

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;
    }
}

Przy takim podejściu nadal musisz znać liczbę i typ parametrów, które należy przekazać do każdej funkcji w odpowiednim indeksie słownika, w przeciwnym razie wystąpi błąd w czasie wykonywania. A jeśli twoje funkcje nie mają wartości zwracanych, użyj System.Action<>zamiast System.Func<>.

Darin Dimitrov
źródło
Widzę. Myślę, że będę musiał wtedy poświęcić trochę czasu na czytanie o refleksji, doceniam pomoc.
chi
@chi, zobacz moją ostatnią aktualizację. Dodałem przykład z Dictionary<int, Delegate>.
Darin Dimitrov
3
Nie będę głosował przeciw, ale muszę tylko powiedzieć, że tego rodzaju implementacja jest anty-wzorcem bez bardzo dobrych powodów. Jeśli OP oczekuje, że klienci przekażą wszystkie argumenty do funkcji, to po co w pierwszej kolejności mieć tabelę funkcji? Dlaczego nie miałby klienta po prostu wywołać funkcje bez magii dynamicznych wywołań ?
Julia
1
@Juliet, zgadzam się z tobą, nigdy nie powiedziałem, że to dobra rzecz. Przy okazji w mojej odpowiedzi podkreśliłem, że nadal musimy znać liczbę i rodzaj parametrów, które należy przekazać każdej funkcji w odpowiednim indeksie słownika. Masz absolutną rację, wskazując, że w tym przypadku prawdopodobnie nie potrzebujemy nawet tablicy haszującej, ponieważ moglibyśmy bezpośrednio wywołać funkcję.
Darin Dimitrov
1
A jeśli potrzebuję zapisać funkcję, która nie przyjmuje żadnych argumentów i nie ma wartości zwracanej?
DataGreed
8

Jednak sygnatura funkcji nie zawsze jest taka sama, przez co ma różną liczbę argumentów.

Zacznijmy od kilku funkcji zdefiniowanych w ten sposób:

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

Naprawdę masz do dyspozycji 2 realne opcje:

1) Zachowaj bezpieczeństwo typów, prosząc klientów o bezpośrednie wywoływanie Twojej funkcji.

To chyba najlepsze rozwiązanie, chyba że masz bardzo dobre powody, by oderwać się od tego modelu.

Kiedy mówisz o chęci przechwytywania wywołań funkcji, wydaje mi się, że próbujesz ponownie wymyślić funkcje wirtualne. Istnieje mnóstwo sposobów na uzyskanie tego rodzaju funkcjonalności po wyjęciu z pudełka, takich jak dziedziczenie z klasy bazowej i nadpisywanie jej funkcji.

Wydaje mi się, że potrzebujesz klasy, która jest bardziej opakowaniem niż wyprowadzona instancja klasy bazowej, więc zrób coś takiego:

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) LUB zmapuj wejście funkcji do wspólnego interfejsu.

Może to zadziałać, jeśli wszystkie funkcje są ze sobą powiązane. Na przykład, jeśli piszesz grę, a wszystkie funkcje robią coś z jakąś częścią ekwipunku gracza lub gracza. Skończyłbyś z czymś takim:

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);
    }
}
Julia
źródło
Moje założenie byłoby objectpodobne do tego, które zostanie przekazane do nowego wątku.
Scott Fraley
1

Hej, mam nadzieję, że to pomoże. Z jakiego języka pochodzisz?

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);
    }
}
smartcaveman
źródło
1

Dlaczego nie użyć params object[] listdla parametrów metod i przeprowadzić walidację wewnątrz metod (lub logiki wywoływania), pozwoli to na zmienną liczbę parametrów.

wvd_vegt
źródło
1

Poniższy scenariusz umożliwiłby użycie słownika elementów do przesłania jako parametrów wejściowych i uzyskania tego samego, co parametry wyjściowe.

Najpierw dodaj następujący wiersz u góry:

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

Następnie w klasie zdefiniuj słownik w następujący sposób:

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

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

Umożliwiłoby to uruchamianie tych anonimowych funkcji ze składnią podobną do tej:

var output = actions["runproc"](inputparam);
Farax
źródło
0

Zdefiniuj słownik i dodaj odwołanie do funkcji jako wartość, używając System.Actionjako typu:

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
    }
}

Następnie wywołaj go za pomocą:

Actions.myActions["myKey"]();
model13
źródło