Funciones de almacenamiento de C # en un diccionario

93

¿Cómo creo un diccionario donde puedo almacenar funciones?

Gracias.

Tengo más de 30 funciones que se pueden ejecutar desde el usuario. Quiero poder ejecutar la función de esta manera:

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

Sin embargo, la firma de la función no es siempre la misma, por lo que tiene una cantidad diferente de argumentos.

chi
fuente
5
Este es un gran modismo: puede reemplazar las desagradables declaraciones de cambio.
Hamish Grubijan
Su función de ejemplo tiene parámetros, sin embargo, cuando invoca la función almacenada, la está invocando sin ningún argumento. ¿Los argumentos son fijos cuando se almacena la función?
Tim Lloyd
1
@HamishGrubijan, si hace esto para reemplazar una declaración de cambio, entonces está descartando toda la optimización y claridad del tiempo de compilación. Entonces, yo diría que fue más una idiotez que una idiotez. Si desea mapear dinámicamente la funcionalidad de una manera que podría variar en tiempo de ejecución, entonces podría ser útil.
Jodrell
12
@Jodrell, A) Idiota es una palabra fuerte que no es constructiva, B) La claridad está en los ojos del espectador. He visto muchas declaraciones de cambio desagradables. C) Optimización de tiempo de compilación ... Zooba en este hilo argumenta a favor de lo contrario stackoverflow.com/questions/505454/… Si las declaraciones de cambio son O (log N), entonces tendría que contener más de 100 casos para que la velocidad marque la diferencia . Eso no es legible, por supuesto. Tal vez una sentencia de cambio pueda utilizar una función hash perfecta, pero solo para una pequeña cantidad de casos. Si está utilizando .Net, entonces no se preocupe por los microsegundos.
Hamish Grubijan
4
@Jodrell, (continuación) Si desea aprovechar al máximo su hardware, utilice ASM, C o C ++ en ese orden. La búsqueda de diccionario en .Net no lo matará. Varias firmas de los delegados: ese es el punto más fuerte que hizo en contra de utilizar un método de diccionario simple.
Hamish Grubijan

Respuestas:

120

Me gusta esto:

Dictionary<int, Func<string, bool>>

Esto le permite almacenar funciones que toman un parámetro de cadena y devuelven booleanos.

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

O si la función no es anónima:

dico[5] = Foo;

donde Foo se define así:

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

ACTUALIZAR:

Después de ver su actualización, parece que no conoce de antemano la firma de la función que le gustaría invocar. En .NET para invocar una función es necesario pasar todos los argumentos y si no sabe cuáles serán los argumentos, la única forma de lograrlo es mediante la reflexión.

Y aquí hay otra 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 este enfoque, aún necesita saber el número y el tipo de parámetros que deben pasarse a cada función en el índice correspondiente del diccionario o obtendrá un error de tiempo de ejecución. Y si sus funciones no tienen valores de retorno, use en System.Action<>lugar de System.Func<>.

Darin Dimitrov
fuente
Veo. Supongo que tendré que dedicar un tiempo a leer sobre la reflexión, agradeceré la ayuda.
chi
@chi, mira mi última actualización. Agregué un ejemplo con Dictionary<int, Delegate>.
Darin Dimitrov
3
No votaré negativamente, pero tendré que decir que este tipo de implementación es un antipatrón sin muy buenas razones. Si el OP espera que los clientes pasen todos los argumentos a la función, entonces, ¿por qué tener una tabla de funciones en primer lugar? ¿Por qué no hacer que el cliente simplemente llame a las funciones sin la magia de las invocaciones dinámicas?
Juliet
1
@Juliet, estoy de acuerdo contigo, nunca dije que eso fuera algo bueno. Por cierto, en mi respuesta hice hincapié en el hecho de que todavía necesitamos saber el número y el tipo de parámetros que deben pasarse a cada función en el índice correspondiente del diccionario. Tiene toda la razón al señalar que en este caso probablemente ni siquiera necesitemos una tabla hash, ya que podríamos invocar directamente la función.
Darin Dimitrov
1
¿Qué sucede si necesito almacenar una función que no toma ningún argumento y no tiene valor de retorno?
DataGreed
8

Sin embargo, la firma de la función no es siempre la misma, por lo que tiene una cantidad diferente de argumentos.

Comencemos con algunas funciones definidas así:

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

Realmente tienes 2 opciones viables a tu disposición:

1) Mantenga la seguridad de tipos haciendo que los clientes llamen directamente a su función.

Esta es probablemente la mejor solución, a menos que tenga muy buenas razones para romper con este modelo.

Cuando hablas de querer interceptar llamadas a funciones, me parece que estás tratando de reinventar funciones virtuales. Hay un montón de formas de sacar este tipo de funcionalidad de la caja, como heredar de una clase base y anular sus funciones.

Me parece que quieres una clase que sea más un contenedor que una instancia derivada de una clase base, así que haz algo como esto:

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) O asigne la entrada de sus funciones a una interfaz común.

Esto podría funcionar si todas sus funciones están relacionadas. Por ejemplo, si está escribiendo un juego y todas las funciones afectan a alguna parte del jugador o del inventario del jugador. Terminarías con algo como esto:

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);
    }
}
Julieta
fuente
Mi suposición sería objectsimilar a la que se pasaría a un nuevo hilo.
Scott Fraley
1

Oye, espero que esto te ayude. ¿De qué idioma vienes?

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);
    }
}
inteligentecaveman
fuente
1

¿Por qué no usar params object[] listpara los parámetros del método y hacer alguna validación dentro de sus métodos (o lógica de llamada)? Permitirá un número variable de parámetros.

wvd_vegt
fuente
1

El siguiente escenario le permitiría usar un diccionario de elementos para enviar como parámetros de entrada y obtener lo mismo que los parámetros de salida.

Primero agregue la siguiente línea en la parte superior:

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

Luego, dentro de su clase, defina el diccionario de la siguiente manera:

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

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

Esto le permitiría ejecutar estas funciones anónimas con una sintaxis similar a esta:

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

Defina el diccionario y agregue la referencia de la función como valor, usando System.Actioncomo 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
    }
}

Luego invocalo con:

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