Fonctions de stockage C # dans un dictionnaire

93

Comment créer un dictionnaire dans lequel je peux stocker des fonctions?

Merci.

J'ai environ 30+ fonctions qui peuvent être exécutées par l'utilisateur. Je veux pouvoir exécuter la fonction de cette façon:

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

Cependant, la signature de la fonction n'est pas toujours la même, ayant donc un nombre d'arguments différent.

chi
la source
5
C'est un bon idiome - peut remplacer les instructions de commutation désagréables.
Hamish Grubijan
Votre exemple de fonction a des paramètres, cependant, lorsque vous appelez la fonction stockée, vous l'appelez sans aucun argument. Les arguments sont-ils fixes lorsque la fonction est stockée?
Tim Lloyd
1
@HamishGrubijan, si vous faites cela pour remplacer une instruction switch, vous supprimez toute l'optimisation et la clarté de la compilation. Donc, je dirais que c'était plus idiot que idiome. Si vous souhaitez mapper dynamiquement les fonctionnalités d'une manière qui pourrait varier au moment de l'exécution, cela peut être utile.
Jodrell
12
@Jodrell, A) Idiot est un mot fort qui n'est pas constructif, B) La clarté est aux yeux d'un spectateur. J'ai vu beaucoup de déclarations de commutateur laides. C) Optimisation du temps de compilation ... Zooba sur ce thread plaide pour l'opposé stackoverflow.com/questions/505454/... Si les instructions switch sont O (log N), alors il faudrait qu'il contienne plus de 100 cas pour que la vitesse fasse une différence . Ce n'est pas lisible, bien sûr. Peut-être qu'une instruction switch peut utiliser une fonction de hachage parfaite, mais seulement pour un petit nombre de cas. Si vous utilisez .Net, vous ne vous inquiétez pas pendant les microsecondes.
Hamish Grubijan
4
@Jodrell, (suite) Si vous souhaitez tirer le meilleur parti de votre matériel, vous utilisez ASM, C ou C ++ dans cet ordre. La recherche de dictionnaire dans .Net ne vous tuera pas. Différentes signatures des délégués - c'est le point le plus fort que vous avez fait contre l'utilisation d'une approche simple par dictionnaire.
Hamish Grubijan

Réponses:

120

Comme ça:

Dictionary<int, Func<string, bool>>

Cela vous permet de stocker des fonctions qui acceptent un paramètre de chaîne et renvoient une valeur booléenne.

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

Ou si la fonction n'est pas anonyme:

dico[5] = Foo;

où Foo est défini comme ceci:

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

METTRE À JOUR:

Après avoir vu votre mise à jour, il semble que vous ne connaissez pas à l'avance la signature de la fonction que vous souhaitez invoquer. Dans .NET, pour appeler une fonction, vous devez passer tous les arguments et si vous ne savez pas quels arguments vont être, le seul moyen d'y parvenir est la réflexion.

Et voici une autre 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;
    }
}

Avec cette approche, vous devez toujours connaître le nombre et le type de paramètres qui doivent être passés à chaque fonction à l'index correspondant du dictionnaire ou vous obtiendrez une erreur d'exécution. Et si vos fonctions n'ont pas de valeurs de retour, utilisez à la System.Action<>place de System.Func<>.

Darin Dimitrov
la source
Je vois. Je suppose que je devrai passer un peu de temps à lire sur la réflexion alors, apprécier l'aide.
chi
@chi, voir ma dernière mise à jour. J'ai ajouté un exemple avec Dictionary<int, Delegate>.
Darin Dimitrov
3
Je ne voterai pas contre, mais je devrai simplement dire que ce type de mise en œuvre est un anti-modèle sans très bonnes raisons. Si l'OP s'attend à ce que les clients transmettent tous les arguments à la fonction, alors pourquoi avoir une table de fonctions en premier lieu? Pourquoi ne pas demander au client d' appeler simplement les fonctions sans la magie des appels dynamiques?
Juliette
1
@Juliet, je suis d'accord avec toi, je n'ai jamais dit que c'était une bonne chose. Au fait, dans ma réponse, j'ai insisté sur le fait que nous avons encore besoin de connaître le nombre et le type de paramètres qui doivent être passés à chaque fonction à l'index correspondant du dictionnaire. Vous avez tout à fait raison en soulignant que dans ce cas, nous n'avons probablement même pas besoin d'une table de hachage car nous pourrions appeler directement la fonction.
Darin Dimitrov
1
Que faire si j'ai besoin de stocker une fonction qui ne prend aucun argument et n'a pas de valeur de retour?
DataGreed
8

Cependant, la signature de la fonction n'est pas toujours la même, ayant donc un nombre d'arguments différent.

Commençons par quelques fonctions définies comme ceci:

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

Vous avez vraiment 2 options viables à votre disposition:

1) Maintenez la sécurité de type en demandant aux clients d'appeler directement votre fonction.

C'est probablement la meilleure solution, sauf si vous avez de très bonnes raisons de rompre avec ce modèle.

Lorsque vous parlez de vouloir intercepter des appels de fonction, il me semble que vous essayez de réinventer des fonctions virtuelles. Il existe une multitude de façons d'obtenir ce type de fonctionnalité hors de la boîte, comme hériter d'une classe de base et remplacer ses fonctions.

Il me semble que vous voulez une classe qui est plus un wrapper qu'une instance dérivée d'une classe de base, alors faites quelque chose comme ceci:

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) OU mappez l'entrée de vos fonctions sur une interface commune.

Cela peut fonctionner si toutes vos fonctions sont liées. Par exemple, si vous écrivez un jeu et que toutes les fonctions affectent une partie de l'inventaire du joueur ou du joueur. Vous vous retrouveriez avec quelque chose comme ça:

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);
    }
}
Juliette
la source
Mon hypothèse serait objectsemblable à celle qui serait passée à un nouveau fil.
Scott Fraley
1

Hé, j'espère que ça aide. De quelle langue venez-vous?

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
la source
1

Pourquoi ne pas utiliser params object[] listpour les paramètres de méthode et faire une validation à l'intérieur de vos méthodes (ou de la logique d'appel), cela permettra un nombre variable de paramètres.

wvd_vegt
la source
1

Le scénario suivant vous permettrait d'utiliser un dictionnaire d'éléments à envoyer en tant que paramètres d'entrée et d'obtenir le même que les paramètres de sortie.

Ajoutez d'abord la ligne suivante en haut:

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

Ensuite, dans votre classe, définissez le dictionnaire comme suit:

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

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

Cela vous permettrait d'exécuter ces fonctions anonymes avec une syntaxe similaire à celle-ci:

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

Définissez le dictionnaire et ajoutez la référence de fonction comme valeur, en utilisant System.Actioncomme type:

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

Puis invoquez-le avec:

Actions.myActions["myKey"]();
modle13
la source