Funções C # Store em um Dicionário


93

Como faço para criar um dicionário onde posso armazenar funções?

Obrigado.

Tenho cerca de 30 funções que podem ser executadas pelo usuário. Eu quero ser capaz de executar a função desta forma:

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

No entanto, a assinatura da função nem sempre é a mesma, tendo, portanto, diferentes quantidades de argumentos.


5
Este é um ótimo idioma - pode substituir instruções de switch desagradáveis.
Hamish Grubijan de

Sua função de exemplo tem parâmetros, entretanto, quando você invoca a função armazenada, você a invoca sem nenhum argumento. Os argumentos são fixos quando a função é armazenada?
Tim Lloyd de

1
@HamishGrubijan, se você fizer isso para substituir uma instrução switch, estará descartando toda a otimização e clareza do tempo de compilação. Então, eu diria que foi mais idiota do que idiota. Se você deseja mapear dinamicamente a funcionalidade de uma forma que pode variar no tempo de execução, pode ser útil.
Jodrell

12
@Jodrell, A) Idiota é uma palavra forte que não é construtiva, B) A clareza está nos olhos de um observador. Já vi muitas declarações de switch feias. C) Otimização do tempo de compilação ... Zooba neste encadeamento defende o oposto stackoverflow.com/questions/505454/… Se as instruções switch forem O (log N), então teria que conter mais de 100 casos para que a velocidade fizesse a diferença . Isso não é legível, é claro. Talvez uma instrução switch possa utilizar uma função hash perfeita, mas apenas para um pequeno número de casos. Se você estiver usando .Net, não se preocupe com microssegundos.
Hamish Grubijan

4
@Jodrell, (continuação) Se você quiser aproveitar ao máximo o seu hardware, use ASM, C ou C ++ nessa ordem. A pesquisa de dicionário em .Net não o matará. Várias assinaturas dos delegados - esse é o ponto mais forte que você fez contra o uso de uma abordagem de dicionário simples.
Hamish Grubijan

Respostas:


120

Como isso:

Dictionary<int, Func<string, bool>>

Isso permite que você armazene funções que recebem um parâmetro de string e retornam booleano.

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

Ou se a função não for anônima:

dico[5] = Foo;

onde Foo é definido assim:

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

ATUALIZAR:

Depois de ver sua atualização, parece que você não sabe com antecedência a assinatura da função que deseja invocar. No .NET para invocar uma função, você precisa passar todos os argumentos e se você não sabe quais são os argumentos, a única maneira de conseguir isso é através da reflexão.

E aqui está outra 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;
    }
}

Com essa abordagem, você ainda precisa saber o número e o tipo de parâmetros que precisam ser passados ​​para cada função no índice correspondente do dicionário ou obterá um erro de execução. E se suas funções não tiverem valores de retorno, use em System.Action<>vez de System.Func<>.


Entendo. Acho que terei que passar algum tempo lendo sobre reflexão então, agradeço a ajuda.
chi de

@chi, veja minha última atualização. Eu adicionei um exemplo com Dictionary<int, Delegate>.
Darin Dimitrov

3
Eu não vou downvote, mas eu só tenho a dizer este tipo de aplicação é um antipattern sem muito boas razões. Se o OP espera que os clientes passem todos os argumentos para a função, então por que ter uma tabela de funções em primeiro lugar? Por que não fazer o cliente simplesmente chamar as funções sem a mágica das chamadas dinâmicas?
Julieta de

1
@Juliet, concordo com você, nunca disse que isso é bom. A propósito, em minha resposta, enfatizei o fato de que ainda precisamos saber o número e o tipo de parâmetros que precisam ser passados ​​para cada função no índice correspondente do dicionário. Você está absolutamente correto ao apontar que, neste caso, provavelmente nem precisamos de uma tabela de hash, pois poderíamos invocar diretamente a função.
Darin Dimitrov de

1
E se eu precisar armazenar uma função que não receba nenhum argumento e não tenha valor de retorno?
DataGreed

8

No entanto, a assinatura da função nem sempre é a mesma, tendo, portanto, diferentes quantidades de argumentos.

Vamos começar com algumas funções definidas assim:

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

Você realmente tem 2 opções viáveis ​​à sua disposição:

1) Mantenha a segurança de tipo fazendo com que os clientes chamem sua função diretamente.

Esta é provavelmente a melhor solução, a menos que você tenha muito boas razões para quebrar a partir deste modelo.

Quando você fala sobre como interceptar chamadas de função, parece-me que você está tentando reinventar funções virtuais. Existem várias maneiras de tirar esse tipo de funcionalidade da caixa, como herdar de uma classe base e substituir suas funções.

Parece-me que você quer uma classe que seja mais um wrapper do que uma instância derivada de uma classe base, então faça algo assim:

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 mapeie a entrada de suas funções para uma interface comum.

Isso pode funcionar se todas as suas funções estiverem relacionadas. Por exemplo, se você está escrevendo um jogo e todas as funções fazem algo para alguma parte do jogador ou inventário do jogador. Você acabaria com algo assim:

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

Minha suposição seria objectsemelhante àquela que seria passada para um novo Thread.
Scott Fraley

1

Ei, espero que isso ajude. De qual idioma você vem?

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

Por que não usar params object[] listpara parâmetros de método e fazer alguma validação dentro de seus métodos (ou lógica de chamada), isso permitirá um número variável de parâmetros.


1

O cenário a seguir permite que você use um dicionário de elementos para enviar como parâmetros de entrada e obter o mesmo que os parâmetros de saída.

Primeiro, adicione a seguinte linha no topo:

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

Então, dentro de sua classe, defina o dicionário da seguinte maneira:

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

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

Isso permitiria que você execute essas funções anônimas com uma sintaxe semelhante a esta:

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

0

Defina o dicionário e adicione a referência da função como o valor, usando System.Actioncomo o 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
    }
}

Em seguida, invoque-o com:

Actions.myActions["myKey"]();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.