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.


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


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

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

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.


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

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"]();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.