Funzioni di archivio C # in un dizionario


93

Come creo un dizionario in cui posso memorizzare le funzioni?

Grazie.

Ho circa 30+ funzioni che possono essere eseguite dall'utente. Voglio essere in grado di eseguire la funzione in questo modo:

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

Tuttavia, la firma della funzione non è sempre la stessa, quindi ha una quantità diversa di argomenti.


5
Questo è un ottimo idioma: può sostituire le brutte dichiarazioni di switch.
Hamish Grubijan

La funzione di esempio ha parametri, tuttavia, quando si richiama la funzione memorizzata la si richiama senza argomenti. Gli argomenti sono corretti quando la funzione viene memorizzata?
Tim Lloyd

1
@HamishGrubijan, se lo fai per sostituire un'istruzione switch, stai scartando tutta l'ottimizzazione e la chiarezza del tempo di compilazione. Quindi, direi che è stato più idiota che idioma. Se si desidera mappare dinamicamente la funzionalità in un modo che potrebbe variare in fase di esecuzione, potrebbe essere utile.
Jodrell

12
@ Jodrell, A) Idiot è una parola forte che non è costruttiva, B) La chiarezza è negli occhi di chi guarda. Ho visto molte brutte dichiarazioni di cambio. C) Ottimizzazione del tempo di compilazione ... Zooba su questo thread sostiene l'opposto stackoverflow.com/questions/505454/… Se le istruzioni switch sono O (log N), allora dovrebbe contenere più di 100 casi affinché la velocità faccia la differenza . Ovviamente non è leggibile. Forse un'istruzione switch può utilizzare una funzione hash perfetta, ma solo per un numero limitato di casi. Se stai usando .Net, non ti preoccupare per i microsecondi.
Hamish Grubijan

4
@ Jodrell, (continua) Se vuoi ottenere il massimo dal tuo hardware, allora usi ASM, C o C ++ in quest'ordine. La ricerca nel dizionario in .Net non ti ucciderà. Varie firme dei delegati: questo è il punto di forza che hai espresso contro un semplice approccio del dizionario.
Hamish Grubijan

Risposte:


120

Come questo:

Dictionary<int, Func<string, bool>>

Ciò consente di memorizzare funzioni che accettano un parametro stringa e restituiscono un valore booleano.

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

Oppure se la funzione non è anonima:

dico[5] = Foo;

dove Foo è definito così:

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

AGGIORNARE:

Dopo aver visto il tuo aggiornamento sembra che tu non conosca in anticipo la firma della funzione che vorresti richiamare. In .NET per richiamare una funzione è necessario passare tutti gli argomenti e se non si sa quali saranno gli argomenti l'unico modo per ottenerlo è attraverso la riflessione.

Ed ecco un'altra alternativa:

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

Con questo approccio è ancora necessario conoscere il numero e il tipo di parametri che devono essere passati a ciascuna funzione nell'indice corrispondente del dizionario o si otterrà un errore di runtime. E se le tue funzioni non hanno valori di ritorno, usa System.Action<>invece di System.Func<>.


Vedo. Immagino che dovrò dedicare un po 'di tempo alla lettura della riflessione, quindi apprezzare l'aiuto.
chi

@chi, guarda il mio ultimo aggiornamento. Ho aggiunto un esempio con Dictionary<int, Delegate>.
Darin Dimitrov

3
Non voglio downvote, ma mi limito a dire che questo tipo di implementazione è un antipattern senza molto buone ragioni. Se l'OP si aspetta che i client passino tutti gli argomenti alla funzione, allora perché avere una tabella delle funzioni in primo luogo? Perché non fare in modo che il client chiami semplicemente le funzioni senza la magia delle invocazioni dinamiche?
Juliet

1
@ Juliet, sono d'accordo con te, non ho mai detto che sia una buona cosa. A proposito, nella mia risposta ho sottolineato il fatto che abbiamo ancora bisogno di conoscere il numero e il tipo di parametri che devono essere passati a ciascuna funzione all'indice corrispondente del dizionario. Hai assolutamente ragione sottolineando che in questo caso probabilmente non abbiamo nemmeno bisogno di una tabella hash in quanto potremmo invocare direttamente la funzione.
Darin Dimitrov

1
E se avessi bisogno di memorizzare una funzione che non accetta argomenti e non ha valore di ritorno?
DataGreed

8

Tuttavia, la firma della funzione non è sempre la stessa, quindi ha una quantità diversa di argomenti.

Cominciamo con alcune funzioni definite in questo modo:

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

Hai davvero 2 opzioni praticabili a tua disposizione:

1) Mantenere l'indipendenza dai tipi facendo in modo che i client chiamino direttamente la funzione.

Questa è probabilmente la soluzione migliore, se non avete molto buone ragioni per rompere da questo modello.

Quando parli di voler intercettare le chiamate di funzione, mi sembra che tu stia cercando di reinventare le funzioni virtuali. C'è un sacco di modi per ottenere questo tipo di funzionalità fuori dagli schemi, come ereditare da una classe base e sovrascrivere le sue funzioni.

Mi sembra che tu voglia una classe che sia più un wrapper che un'istanza derivata di una classe base, quindi fai qualcosa del genere:

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) OPPURE mappare l'input delle tue funzioni su un'interfaccia comune.

Questo potrebbe funzionare se tutte le tue funzioni sono correlate. Ad esempio, se stai scrivendo un gioco e tutte le funzioni fanno qualcosa per una parte dell'inventario del giocatore o del giocatore. Finiresti con qualcosa del genere:

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

La mia ipotesi sarebbe objectsimile a quella che sarebbe passata a un nuovo Thread.
Scott Fraley

1

Ehi, spero che questo aiuti. Da che lingua vieni?

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

Perché non usare params object[] listper i parametri del metodo e fare un po 'di convalida all'interno dei metodi (o della logica di chiamata), consentirà un numero variabile di parametri.


1

Il seguente scenario consentirebbe di utilizzare un dizionario di elementi da inviare come parametri di input e ottenere lo stesso dei parametri di output.

Per prima cosa aggiungi la seguente riga in alto:

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

Quindi all'interno della tua classe, definisci il dizionario come segue:

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

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

Ciò consentirebbe di eseguire queste funzioni anonime con una sintassi simile a questa:

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

0

Definisci il dizionario e aggiungi il riferimento alla funzione come valore, utilizzando System.Actioncome tipo:

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

Quindi invocalo con:

Actions.myActions["myKey"]();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.