C # Funktionen in einem Wörterbuch speichern

92

Wie erstelle ich ein Wörterbuch, in dem ich Funktionen speichern kann?

Vielen Dank.

Ich habe über 30 Funktionen, die vom Benutzer ausgeführt werden können. Ich möchte die Funktion folgendermaßen ausführen können:

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

Die Funktionssignatur ist jedoch nicht immer gleich und weist daher unterschiedliche Argumente auf.

Chi
quelle
5
Dies ist eine großartige Redewendung - kann böse switch-Anweisungen ersetzen.
Hamish Grubijan
Ihre Beispielfunktion verfügt jedoch über Parameter. Wenn Sie die gespeicherte Funktion aufrufen, rufen Sie sie ohne Argumente auf. Sind die Argumente festgelegt, wenn die Funktion gespeichert wird?
Tim Lloyd
1
@HamishGrubijan, wenn Sie dies tun, um eine switch-Anweisung zu ersetzen, verwerfen Sie die gesamte Optimierung und Klarheit der Kompilierungszeit. Also würde ich sagen, es war mehr Idiot als Idiom. Wenn Sie Funktionen dynamisch so zuordnen möchten, dass sie zur Laufzeit variieren können, ist dies möglicherweise hilfreich.
Jodrell
12
@ Jodrell, A) Idiot ist ein starkes Wort, das nicht konstruktiv ist. B) Klarheit ist in den Augen eines Betrachters. Ich habe viele hässliche switch-Anweisungen gesehen. C) Optimierung der Kompilierungszeit ... Zooba in diesem Thread argumentiert für das Gegenteil von stackoverflow.com/questions/505454/…. Wenn switch-Anweisungen O (log N) sind, müsste es mehr als 100 Fälle enthalten, damit die Geschwindigkeit einen Unterschied macht . Das ist natürlich nicht lesbar. Vielleicht kann eine switch-Anweisung eine perfekte Hash-Funktion verwenden, aber nur für eine kleine Anzahl von Fällen. Wenn Sie .Net verwenden, ärgern Sie sich nicht über Mikrosekunden.
Hamish Grubijan
4
@Jodrell, (Fortsetzung) Wenn Sie Ihre Hardware optimal nutzen möchten, verwenden Sie ASM, C oder C ++ in dieser Reihenfolge. Die Wörterbuchsuche in .Net bringt Sie nicht um. Verschiedene Unterschriften der Delegierten - das ist der stärkste Punkt, den Sie gegen die Verwendung eines einfachen Wörterbuchansatzes gemacht haben.
Hamish Grubijan

Antworten:

119

So was:

Dictionary<int, Func<string, bool>>

Auf diese Weise können Sie Funktionen speichern, die einen Zeichenfolgenparameter verwenden und einen Booleschen Wert zurückgeben.

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

Oder wenn die Funktion nicht anonym ist:

dico[5] = Foo;

wo Foo so definiert ist:

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

AKTUALISIEREN:

Nachdem Sie Ihr Update gesehen haben, scheinen Sie die Signatur der Funktion, die Sie aufrufen möchten, nicht im Voraus zu kennen. In .NET müssen Sie zum Aufrufen einer Funktion alle Argumente übergeben. Wenn Sie nicht wissen, welche Argumente verwendet werden, können Sie dies nur durch Reflektion erreichen.

Und hier ist eine andere Alternative:

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

Bei diesem Ansatz müssen Sie noch die Anzahl und den Typ der Parameter kennen, die an jede Funktion am entsprechenden Index des Wörterbuchs übergeben werden müssen. Andernfalls wird ein Laufzeitfehler angezeigt. Und wenn Ihre Funktionen keine Rückgabewerte haben, verwenden Sie System.Action<>stattdessen System.Func<>.

Darin Dimitrov
quelle
Aha. Ich denke, ich muss dann einige Zeit damit verbringen, über Reflexion zu lesen, und die Hilfe zu schätzen wissen.
Chi
@chi, siehe mein letztes Update. Ich habe ein Beispiel mit hinzugefügt Dictionary<int, Delegate>.
Darin Dimitrov
3
Ich werde nicht ablehnen, aber ich muss nur sagen, dass diese Art der Implementierung ein Antimuster ohne sehr gute Gründe ist. Wenn das OP erwartet, dass Clients alle Argumente an die Funktion übergeben, warum dann überhaupt eine Funktionstabelle? Warum lässt der Client die Funktionen nicht einfach ohne die Magie dynamischer Aufrufe aufrufen ?
Julia
1
@ Julia, ich stimme dir zu, ich habe nie gesagt, dass das eine gute Sache ist. Übrigens habe ich in meiner Antwort betont, dass wir noch die Anzahl und Art der Parameter kennen müssen, die an jede Funktion am entsprechenden Index des Wörterbuchs übergeben werden müssen. Sie haben absolut Recht, wenn Sie darauf hinweisen, dass wir in diesem Fall wahrscheinlich nicht einmal eine Hashtabelle benötigen, da wir die Funktion direkt aufrufen könnten.
Darin Dimitrov
1
Was ist, wenn ich eine Funktion speichern muss, die keine Argumente akzeptiert und keinen Rückgabewert hat?
DataGreed
8

Die Funktionssignatur ist jedoch nicht immer gleich und weist daher unterschiedliche Argumente auf.

Beginnen wir mit einigen Funktionen, die wie folgt definiert sind:

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

Sie haben wirklich 2 realisierbare Optionen zur Verfügung:

1) Sorgen Sie für Typensicherheit, indem Kunden Ihre Funktion direkt aufrufen.

Dies ist wahrscheinlich die beste Lösung, es sei denn, Sie haben sehr gute Gründe, von diesem Modell abzubrechen.

Wenn Sie davon sprechen, Funktionsaufrufe abfangen zu wollen, klingt es für mich so, als würden Sie versuchen, virtuelle Funktionen neu zu erfinden. Es gibt eine Vielzahl von Möglichkeiten, um diese Art von Funktionalität sofort einsatzbereit zu machen, z. B. das Erben von Funktionen von einer Basisklasse und das Überschreiben ihrer Funktionen.

Für mich klingt es so, als ob Sie eine Klasse wollen, die eher ein Wrapper als eine abgeleitete Instanz einer Basisklasse ist. Machen Sie also so etwas:

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) ODER ordnen Sie die Eingabe Ihrer Funktionen einer gemeinsamen Schnittstelle zu.

Dies kann funktionieren, wenn alle Ihre Funktionen miteinander verbunden sind. Wenn Sie beispielsweise ein Spiel schreiben und alle Funktionen einen Teil des Inventars des Spielers oder des Spielers beeinflussen. Sie würden mit so etwas enden:

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
quelle
Meine Annahme wäre eine objectsolche, die an einen neuen Thread weitergegeben würde.
Scott Fraley
1

Hey, ich hoffe das hilft. Aus welcher Sprache kommst du?

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
quelle
1

Warum nicht params object[] listfür Methodenparameter verwenden und eine Validierung innerhalb Ihrer Methoden (oder der Aufruflogik) durchführen? Dies ermöglicht eine variable Anzahl von Parametern.

wvd_vegt
quelle
1

Im folgenden Szenario können Sie ein Wörterbuch mit Elementen verwenden, um sie als Eingabeparameter einzusenden und dieselben Ausgabeparameter zu erhalten.

Fügen Sie zuerst die folgende Zeile oben hinzu:

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

Definieren Sie dann in Ihrer Klasse das Wörterbuch wie folgt:

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

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

Auf diese Weise können Sie diese anonymen Funktionen mit einer ähnlichen Syntax ausführen:

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

Definieren Sie das Wörterbuch und fügen Sie die Funktionsreferenz als Wert hinzu, wobei Sie System.Actionals Typ verwenden:

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

Rufen Sie es dann auf mit:

Actions.myActions["myKey"]();
modle13
quelle